JavaSE知识点笔记

参考学习视频:韩顺平java

ch2 Java概述

JDK:

  1. JDK 的全称(Java Development Kit Java 开发工具包)
    JDK = JRE + java 的开发工具 [java, javac,javadoc,javap 等]
  2. JDK 是提供给 Java 开发人员使用的, 其中包含了 java 的开发工具, 也包括了 JRE。 所以安装了 JDK, 就不用在单独安装 JRE 了。

JRE:

  1. JRE(Java Runtime Environment Java 运行环境)
    JRE = JVM + Java 的核心类库[类]
  2. 包括 Java 虚拟机(JVM Java Virtual Machine)和 Java 程序所需的核心类库等, 如果想要运行一个开发好的 Java 程序,
    计算机中只需要安装 JRE 即可

JDK、JRE、JVM关系

  1. **JDK = JRE + 开发工具集(**例如 Javac,java 编译工具等)
  2. JRE = JVM + Java SE 标准类库(java 核心类库)
  3. 如果只想运行开发好的 .class 文件 只需要 JRE

ch7 面向对象编程(基础)

现有的技术解决问题:

张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。 还有一只叫小花,今年 100 岁,花色。 请编写一个程序, 当用户
输入小猫的名字时, 就显示该猫的名字, 年龄, 颜色。 如果用户输入的小猫名错误, 则显示 张老太没有这只猫猫。

缺点:

  • 不利于数据管理
  • 效率低
  • 不能满足新的需求

数组 ===>

(1)数据类型体现不出来

(2) 只能通过[下标]获取信息,造成变量名字和内容 的对应关系不明确

(3) 不能体现猫的行为

对象在内存中存在形式

image-20230926102500754

Java 内存的结构分析

  1. 栈: 一般存放基本数据类型(局部变量)
  2. 堆: 存放对象(Cat cat , 数组等)
  3. 方法区: 常量池(常量, 比如字符串), 类加载信息
image-20230926105408475

成员方法的好处:

  1. 提高代码复用性
  2. 可以将实现的代码细节封装起来,供其他用户来调用即可

汉诺塔问题:

package ch07;

import java.util.Scanner;

public class HanoiTower {
    public static void main(String[] args) {
        Tower tower = new Tower();
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入汉诺塔牌的个数");
        int num = scanner.nextInt();
        tower.move(num, 'A', 'B', 'C');
    }
}

class Tower {
    // num表示移动的个数,a,b,c 分别表示 A,B,C塔。将a移动到c,借助b
    public void move(int num, char a, char b, char c) {
        if(num == 1) {
            System.out.println(a + "->" + c);
        }else {
            /*如果有多个盘,可以看成两个,最下面和上面的所有盘(num - 1)*/
            /*(1)先移动上面的所有盘到b, 借助c*/
            move(num - 1, a, c, b);
            /*(2)把最下面的盘,移动到c*/
            System.out.println(a + "->" + c);
            /*(3)再把b中所有盘,移动到c,借助a*/
            move(num - 1, b, a, c);
        }
    }
}

变量的作用域

image-20230926215651237

image-20230926215702104

构造器

image-20230926220429625

对象创建以及构造器赋值的流程分析

image-20230926221430169 image-20230926221452644

this 的注意事项和使用细节

  1. this 关键字可以用来访问本类的属性、 方法、 构造器

  2. this 用于区分当前类的属性和局部变量

  3. 访问成员方法的语法: this.方法名(参数列表);

  4. 访问构造器语法: this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一
    条语句)

  5. this 不能在类定义的外部使用, 只能在类定义的方法中使用。

ch8 面向对象编程(中级)

Idea 常用快捷键

ctrl + D 删除当前行(自己设定的)

Ctrl+Alt+L 快速格式化代码

ctrl + alt+ ⬇ 复制到下一行 (自个设定的)

ctrl + alt + t 生成for、while、try catch等关键字

ctrl + j 显示所有的快捷键

查看一个类的层级关系 ctrl + H [学习继承后, 非常有用]
将光标放在一个方法上, 输入 ctrl + B , 可以定位到方法 [学继承后, 非常有用]

shift + Enter 直接换到下一行

ctrl + W 选中代码区域

ctrl + alt + ⬅/➡ 回到上次查看代码位置

ctrl+q 查看方法的文档注释

包的命名

image-20230927200130919

访问修饰符范围

image-20230927200416515

注意事项

image-20230927201835054

封装

image-20230927204000186

image-20230927204012202 image-20230927204030935

继承

image-20230927213043982

继承带来的便利:

  1. 代码的复用性提高了
  2. 代码的扩展性和维护性提高

细节问题:

  1. 子类继承了所有的属性和方法, 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访
    问, 要通过父类提供公共的方法去访问
  2. 子类必须用父类的构造器, 完成父类的初始化 。(即先调用父类的构造器,再调用子类的构造器)
  3. 当创建子类对象时, 不管使用子类的哪个构造器, 默认情况下总会去调用父类的无参构造器[ 在子类构造器中有隐藏的super(),即调用父类的构造器 ], 如果父类没有提供无参构造器, 则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作, 否则, 编译不会通过。
  4. 如果希望指定去调用父类的某个构造器, 则显式的调用一下 : super(参数列表)
  5. super 在使用时, 必须放在构造器第一行(super 只能在构造器中使用)
  6. super() 和 this() 都只能放在构造器第一行, 因此这两个方法不能共存在一个构造器
  7. java 所有类都是 Object 类的子类, Object 是所有类的基类.
  8. 父类构造器的调用不限于直接父类! 将一直往上追溯直到 Object 类(顶级父类)
  9. 子类最多只能继承一个父类(指直接继承), 即 java 中是单继承机制。
  10. 不能滥用继承, 子类和父类之间必须满足 is-a 的逻辑关系 比如:Cat extends Animal 猫是一只动物(√) Person extends Music 人是一首音乐(×)
继承的本质分析

当子类继承父类,创建子类对象时,内存中的发生情况:建立其查找的关系

image-20231009171751392

当子类访问属性信息时:

(1) 首先看子类是否有该属性
(2) 如果子类有这个属性, 并且可以访问, 则返回信息
(3) 如果子类没有这个属性, 就看父类有没有这个属性(如果父类有该属性, 并且可以访问, 就返回信息…)
(4) 如果父类没有就按照(3)的规则, 继续找上级父类, 直到 Object…

如果当父类的对象某属性为private修饰的时候,子类不可以访问该属性,只能通过public方法来返回。如 pulibc getAge()

课堂练习
案例1

main方法中:B b = new B(); 会输出什么

image-20231009172525463

在B(String name)方法中有隐藏的super()。

因为B( )方法中有this()方法了,就不能再有super了。

输出 a,b name, b

案例2

main方法中:C c = new C(); 会输出什么

image-20231009174057408

输出:

我是A类
hahah我是B类的有参构造
hahah我是c类的有参构造
我是c类的无参构造

super关键字

super 代表父类的引用, 用于访问父类的属性、 方法、 构造器

image-20231009200441046
super给编程带来的便利
image-20231009203449013
super和this的比较
image-20231009203744107

重写/覆盖(override)

image-20231009205624527 image-20231009205643297
重写和重载的比较
image-20231009205913602

多态

image-20231009212724555

代码复用性不高,不利于代码维护。

多态就是多种状态,在重载和重写中可以体现。但最重要的还是对象的多态。

多态建立在封装和继承之上。

对象的多态
image-20231009215825644

使用多态的机制来解决主人喂食物的问题

/**
Dog extends Animal
Cat extends Animal
Bone extends Food
Fish extends Food
*/
Dog dog = new Dog("大黄");
Bone bone = new Bone("大棒骨");
tom.feed(dog, bone); /*传子类对象类型*/

Cat cat = new Cat("小猫");
Fish fish = new Fish("小黄鱼");
tom.feed(cat, fish);

public void feed(Animal animal, Food food) { //只需定义 animal food 即可,避免写多个feed方法对应不同类型的对象
    System.out.println(animal.getName() + "吃" + food.getName());
}
多态向上转型

前提:两个对象(类)存在继承关系

image-20231009222748446
多态向下转型
image-20231010141944523

接上面代码

Amimal animal = new Cat(); // 向上转型
Cat cat = (Cat)animal; // 向下转型
image-20231010142154907

注意:

  • 第二点,如上图,只能强转引用,而不能改变原有的对象。
  • 第三点,只能将animal转换成Cat,而不能转换成Dog。
多态的细节

属性没有重写之说! 属性的值看编译类型。

instanceOf 比较操作符, 用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型

AA aa = new AA();
System.out.println(aa instanceof AA); // true
AA aa2 = new BB();
System.out.println(aa2 instanceof BB); // true
课堂练习
image-20231010144046010
多态的动态绑定机制
  1. 调用方法的时候,该方法会和该对象的内存地址/运行类型绑定。(先看运行类型)

    举例:设Base base = new Sub(); Sub为子类,Base为父类。 当调用方法1时,子类不存在,则查找父类方法,父类方法有方法1,但方法1中调用了方法2,该方法2子类和父类都有。由于动态绑定,则方法2是调用子类,即运行类型。

  2. 当调用**对象属性的时,没有动态绑定机制,**哪里声明,哪里使用。

    举例:假如调用的方法使用到了某属性,该属性在该类有声明,则直接使用该声明的属性值。因为对象属性没有动态绑定机制。

多态的应用
多态数组
public static void main(String[] args) {
        Person[] persons = new Person[5];
        persons[0] = new Person("jack", 20);
        persons[1] = new Student("mary", 18, 100);
        persons[2] = new Student("smith", 19, 30.1);
        persons[3] = new Teacher("scott", 30, 20000);
        persons[4] = new Teacher("king", 50, 25000);
        for (int i = 0; i < persons.length; i++) {
            System.out.println(persons[i].say()); 
            if(persons[i] instanceof Teacher) {
                ((Teacher) persons[i]).teach();
            }else if(persons[i] instanceof Student) {
                ((Student) persons[i]).study();
            }else {
                System.out.println("有误");
            }
        }
    }
多态参数

方法定义的形参为父类,实参类型(即传递的真实的参数)允许为子类。

Oject类详解

equals方法
image-20231010164431350

把光标放在equals方法,按ctrl+b,即可查看源码。

// Object的equals方法源码
public boolean equals(Object anObject) {
	if (this == anObject) {//如果是同一个对象,即指向的地址相同
		return true;//返回 true
}
    
// String类的重写equals方法,变成比较两个字符串的值是否相同
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    
 
// 重写equals方法,判断值相等
public boolean equals(Object obj) {
	if (obj instanceof Integer) {
		return value == ((Integer)obj).intValue();韩顺平循序渐进学 Java 零基础
	} 
	return false;
}
image-20231010165306698
hashcode方法

hashcode()返回该对象的哈希码值,支持此方法是为了提高哈希表的性能。

小结:

  • 提高具有哈希结构的容器的效率!
  • 两个引用, 如果指向的是同一个对象, 则哈希值肯定是一样的!
  • 两个引用, 如果指向的是不同对象, 则哈希值是不一样的
  • 哈希值主要根据地址号来的! 不能完全将哈希值等价于地址。
  • 后面在集合中 hashCode 如果需要的话, 也会重写, 在讲解集合时, 老韩在说如何重写 hashCode()
toString方法
  1. 默认返回: 全类名+@+哈希值的十六进制
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

// 例如返回com.czk.object@0x23fb32
  1. 重写 toString 方法, 打印对象或拼接对象时, 都会自动调用该对象的 toString 形式。
@Override
    public String toString() {
        return "Person1{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
  1. 直接输出一个对象时, toString 方法会被默认的调用, 比如 System.out.println(person1);
Finalize方法
package ch08;

public class Finalize_ {
    public static void main(String[] args) {
        Car bmw = new Car("宝马");
        bmw = null; 
        //这时 car 对象就是一个垃圾,垃圾回收器就会回收(销毁)对象, 在销毁对象前, 会调用该对象的 finalize 方法
		//程序员就可以在 finalize 中, 写自己的业务逻辑代码(比如释放资源: 数据库连接,或者打开文件..)
		//如果程序员不重写 finalize,那么就会调用 Object 类的 finalize, 即默认处理
        //但是由于gc算法不会立马释放
        System.gc(); //这里调用gc方法主动释放
        System.out.println("程序退出了...");
    }
}

class Car {
    private String name;

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

    @Override
    protected void finalize() throws Throwable {
        System.out.println("销毁汽车对象" + name);
        System.out.println("释放某些资源");
    }
}

  1. 当对象被回收时, 系统自动调用该对象的 finalize 方法。 子类可以重写该方法
  2. 什么时候被回收: 当某个对象没有任何引用时, 则 jvm 就认为这个对象是一个垃圾对象, 就会使用垃圾回收机制来销毁该对象, 在销毁该对象前, 会先调用 finalize 方法
  3. 垃圾回收机制的调用, 是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制

断点调试

重要提示:在断点调试过程中 ,是运行状态,是以对象的运行类型来执行的。

断点调试快捷键
  • F8:跳过,逐行执行代码
  • F7: 跳入(跳入方法内) alt+shift+f7 强行跳入
  • shift + F8:跳出
  • F9:resume,执行到下一个断点
image-20231011204522172

注意:java默认不允许跳入源码,按一下设置就可以了。

image-20231011210341436
细节

断点可以在 debug 过程中, 动态的下断点

ch9 房屋出租系统

设计框架:分析从上往下,实现从下往上。

image-20231017104834061

代码地址:D:\file\Java学习求职之路\1 java编程基础\韩顺平java教程\Mycode\src\ch08\houserent

ch10 面向对象编程(高级)

类变量

提出问题:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?, 编写程序解决。

image-20231017113722205

静态变量是p1和p2所指向的实例对象共享的

image-20231017113916508

代码

public static void main(String[] args) {
      Student student1 = new Student("a");
      student1.count ++;
      Student student2 = new Student("b");
      student2.count ++;
      Student student3 = new Student("c");
      student3.count ++;
      System.out.println(Student.count); // 3
  }
class Student {
  private String name;

  public static int count = 0;
}

static变量保存在哪有两种说法:1. 堆中 2.方法区的静态域中。 和jdk版本不同而不同

不管static变量在哪里:

  • static类变量是同一个类所有对象共享
  • static类变量在类加载的时候就生成了
什么是类变量?

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

如何定义类变量
image-20231017141926423
如何访问类变量
image-20231017142000668
类变量使用细节
image-20231017142952533

类方法

类方法也叫静态方法。

形式如下:

  • 访问修饰符 static 数据返回类型 方法名(){} [推荐]
  • static 访问修饰符 数据返回类型 方法名(){}

使用方式:类名.类方法名 或者 对象名.类方法名 [前提:满足访问修饰符的访问权限和范围]

class Student {
    public String name;
    public static double totalFee = 0.0;
    public Student(String name) {
        this.name = name;
	}
    public static void payFee(double fee) {
        Stu.totalFee += fee;
	}
    
    public void showFee() {
		System.ou  .println("总学费为...." + Student.totalFee)
    }
}
使用场景
image-20231017145049695
注意和使用细节
image-20231017145532101

重点:1. 静态方法,只能访问静态成员(静态变量和静态方法)。2非静态方法,可以访问所有的成员。 3.在编写代码时,仍然要遵守访问权限规则

main方法

image-20231017152907081

第五点 参数可通过命令行传进去 :

public class Hello {
    public static void main(String[] args) {
        for(int i = 0; i < args.length; i ++) {
            System.out.println("第"+i+"个参数=" + args[i]);
        }
    }
}
image-20231017153037259
特别提示
  1. 在main方法中,可以直接调用main方法所在类的静态方法和静态属性

  2. 但是,不能直接访问该类中的非静态变量,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。

    public class Main01 {
    	private int n1 = 10000;
        public static void main(String[] args) {
    		Main01 main01 = new Main01();
    		System.out.printlin(main01.n1);
        }
    }
    

代码块

代码块又称初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。

image-20231017165015967
代码块的好处
image-20231017165035995

下面构造器中都有相同的语句,这样代码看起来比较冗余 ,这时我们可以把相同的语句, 放入到一个代码块中, 即可 。

这样当我们不管调用哪个构造器, 创建对象, 都会先调用代码块的内容。

代码块调用的顺序优先于构造器 。

public class Movie {
    private String name;
    private double price;
    private String director;

    {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始....");
        System.out.println("电影正式开始....");
    }
    public Movie(String name) {
        //System.out.println("电影屏幕打开...");
        //System.out.println("广告开始....");
        //System.out.println("电影正式开始....");
        this.name = name;
    }

    public Movie(String name, double price) {
        //System.out.println("电影屏幕打开...");
        //System.out.println("广告开始....");
        //System.out.println("电影正式开始....");
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

当调用Movie类的构造器的时候,首先会执行代码块中的内容。

代码块的使用细节
image-20231017172517561

当创建一个对象时,记住一个类的调用顺序

image-20231017213951692

举例代码如下:

package ch10.codeblock_;

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
        return;
    }
}

class A {
    private int n2 = getN2(); // 普通变量初始化
    private static int n1 = getN1(); // 静态变量初始化

    {
        System.out.println("A类普通代码块");
    }
    static  {
        System.out.println("A类静态代码块");
    }

    public static int getN1(){
        System.out.println("getN1 被调用");
        return 100;
    }

    public int getN2() {
        System.out.println("getN2 被调用");
        return 200;
    }
    public A() {
        System.out.println("调用构造方法");
    }
}

/*
输出:
getN1 被调用
A类静态代码块
getN2 被调用
A类普通代码块
调用构造方法
*/
image-20231017215255362

构造器前面包含了super()和调用普通代码块,而静态相关代码块和属性初始化在类加载时就执行完毕了。

举例代码如下:

package ch10.codeblock_;

public class CodeBlockDetail03 {
    public static void main(String[] args) {
        BB bb = new BB();
    }
}

class AA {
    {
        System.out.println("AA类普通代码块");
    }

    public AA() {
        // 1.super()
        // 2.调用本类普通代码块
        System.out.println("AA类构造器");
    }
}

class BB extends AA{
    {
        System.out.println("BB的普通代码块");
    }

    public BB() {
        // 1.super()
        // 2.调用本类普通代码块
        System.out.println("BB构造器");
    }
}

/*
AA类普通代码块
AA类构造器
BB的普通代码块
BB构造器
*/

第6点是综合。(第1.2点应该是静态属性初始化)

image-20231017220142702

单例设计模式

image-20231017222027574
什么是单例模式

单例(单个的实现)

  • 所谓的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
  • 单例模式有两种方式:1)饿汉式 2)懒汉式

步骤:

image-20231017223227535
饿汉式单例

为什么是饿汉式:可能你还没又去使用实例,但是它还是会直接先创建一个实例。

//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是 static)
//3. 提供一个公共的 static 方法, 返回 gf 对象
package ch10.single_;

public class SingleTon01 {
    public static void main(String[] args) {
        GirlFriend instance1 = GirlFriend.getInstance();
        System.out.println(instance1);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
    }
}

class GirlFriend {
    private String name;

    /*私有化构造器,防止new*/
    private GirlFriend(String name) {
        this.name = name;
    }


    /*为了能够在静态方法中, 返回 gf 对象, 需要将其修饰为 static*/
    private static GirlFriend gf = new GirlFriend("lqy");


    /*这里static是因为,如果不是静态方法的话,还是必须new再调用该方法,那就没意义了*/
    public static GirlFriend getInstance() {
        return gf;
    }

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

/**
GirlFriend{name='lqy'}
GirlFriend{name='lqy'}
true
/

单例对象,通常是重量级别的对象,饿汉式可能造成了创建了对象,但是没有使用。

懒汉式单例
package ch10.single_;

public class SingleTon02 {
    public static void main(String[] args) {
        Cat instance1 = Cat.getInstance();
        System.out.println(instance1);
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
    }
}

class Cat {
    private String name;
    private static Cat cat;
    private Cat(String name) {
        System.out.println("构造器被调用");
        this.name = name;
    }

    public static Cat getInstance() {
        if(cat == null) {
            cat = new Cat("小花猫");
        }
        return cat;
    }

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

/**
构造器被调用
Cat{name='小花猫'}
Cat{name='小花猫'}
true
*/

懒汉式,只有当用户使用getInstance时,才能创建并返回Cat对象。

总结
image-20231018115305218

final关键字

image-20231018115926005
使用细节
image-20231018191859526 image-20231018192517068

第7点代码举例:

package ch10.final_;

public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(AA.num);
    }
}

class AA {
    public final static int num = 1000; // 搭配使用不会导致类加载,底层编译器做了优化处理。
    static {
        System.out.println("静态代码块被执行..");
    }
}

/*
输出:1000
没有进行类加载 
*/

抽象类

abstra关键字

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法, 那么这个类就是抽象类。

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法, 这个方法就是抽象方法,用abstract来修饰该类就是抽象类。

image-20231018195238933
使用细节
image-20231018195308167 image-20231018195604093

第8点示例代码

package ch10.abstract_;

public class AbstractDetail02 {
}

abstract class C {
    abstract public void say();
}

abstract class D extends C{

}

class E extends C {
    @Override  
    public void say() {
        System.out.println("你好");
    }
}
image-20231018201154455
练习
image-20231018202002556
抽象类的实践-模板设计模式
image-20231018202519098 image-20231018202533113
实践
image-20231018204225825 image-20231018204240429

代码

package ch10.abstract_;


public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();
        BB bb = new BB();
        bb.calculateTime();
    }
}

abstract class Template {
    public abstract void job(); //抽象方法

    public void calculateTime() {
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间" + (end - start));
    }
}
class AA extends Template{

    @Override
    public void job() {
        long num = 0;
        for (int i = 0; i <= 800000; i++) {
            num += i;
        }
    }
}

class BB extends Template {

    @Override
    public void job() {
        long num = 0;
        for (int i = 0; i <= 800000; i++) {
            num *= i;
        }
    }
}

注意:子类AA和BB调用方法父类的calculateTime方法时,会调用job()方法。此时根据动态绑定机制,由于运行类型是子类,则调用子类的job方法。

接口

image-20231018212044065
使用
image-20231018213911876

如果项目经理要3个程序员写mySQL、Oracle、DB2的数据库连接和关闭方法。则可能会出现3个程序员不同的命名规范。

使用接口则可以更加规范,并且使用起来可以统一管理。

// DBInterface.java
package ch10.interface_;

public interface DBInterface { //项目经理
    public void connect();
    public void close();
}
//MysqlDB.java
package ch10.interface_;

public class MysqlDB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接mysql");
    }

    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}

//OracleDB.java
package ch10.interface_;

public class OracleDB implements DBInterface {
    @Override
    public void connect() {
        System.out.println("连接Orcal");
    }

    @Override
    public void close() {
        System.out.println("关闭Orcal");
    }
}
//Interface03.java
package ch10.interface_;

public class Interface03 {
    public static void main(String[] args) {
        MysqlDB mysqlDB = new MysqlDB();
        t(mysqlDB);
        OracleDB oracleDB = new OracleDB();
        t(oracleDB);
    }

    /*这里静态只是为了不创建Interface03对象*/
    public static void t(DBInterface db) {
        db.connect();
        db.close();
    }

}

/**
输出:
连接mysql
关闭mysql
连接Orcal
关闭Orcal
*/

但是接口的妙用远不止这些。

细节和注意事项
image-20231018215503290 image-20231018220129770
实现接口vs继承类
image-20231018221109954

代码:

package ch10.interface_;

public class ExtendvsInterface {
    public static void main(String[] args) {
        LittleMonkey littleMonkey = new LittleMonkey("小猴子");
        littleMonkey.climb();
        littleMonkey.swimming();
        littleMonkey.flying();
    }
}

class Monkey {
    private String name;

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

    public void climb() {
        System.out.println(name + "会爬树");
    }

    public String getName() {
        return name;
    }

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

interface Fishable {
    void swimming();
}

interface Birdable {
    void flying();
}
class LittleMonkey extends Monkey implements Fishable, Birdable{

    public LittleMonkey(String name) {
        super(name);
    }

    @Override
    public void swimming() {
        System.out.println(getName() + "通过学习,可以像鱼儿一样游泳");
    }

    @Override
    public void flying() {
        System.out.println(getName() + "通过学习,可以像鸟儿一样飞翔");
    }
}

小结:

  • 当子类继承了父类, 就自动的拥有父类的功能。
  • 如果子类需要扩展功能, 可以通过实现接口的方式扩展。
  • 可以理解 实现接口 是 对 java 单继承机制的一种补充。
image-20231018221735783
接口的多态
image-20231018222328300

第一点代码示例:

// 多态1:形参是接口类型,实际参数是实现接口的类的对象
public static void t(DBInterface db) {
        db.connect();
        db.close();
    }
// 多态2:引用指向实现接口的类的对象
Interface01 interface01 = new AAA();

第二点代码:

/*
call()是phone中特定的方法
Phone类和Camea类都重写了work方法
*/
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
for(int i = 0; i < usbs.length; i++) {
    usbs[i].work();//动态绑定..
	//和前面一样, 我们仍然需要进行类型的向下转型
	if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_
		((Phone_) usbs[i]).call(); 
	}	
}
interface Usb{
    void work();
}

第三点代码:

public static void main(String[] args) {
	//接口类型的变量可以指向, 实现了该接口的类的对象实例
	IG ig = new Teacher();
	//如果 IG 继承了 IH 接口, 而 Teacher 类实现了 IG 接口
	//那么, 实际上就相当于 Teacher 类也实现了 IH 接口.
	//这就是所谓的 接口多态传递现象.
	IH ih = new Teacher();
}

interface IH{}
interface IG extends IH{}
class Teacher implement IG{}

内部类

如果定义类在局部位置(方法中/代码块) :(1) 局部内部类 (2) 匿名内部类

定义在成员位置 (1) 成员内部类 (2) 静态内部类

image-20231020121629206 image-20231020121643723
内部类的分类
image-20231020121707545
局部内部类的使用
image-20231020140325653 image-20231020141605040

代码示例:

package ch10.innerclass_;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

public class InnerClass01 {    
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.m1();
    }
}

class Outer {
    private int n1 = 10;

    public void m2() {
        System.out.println("调用外部类的m2");
    }

    public void m1(){
        class inner01 {
            private int n1 = 50;
            public void f1() {
                /*局部内部类可以直接访问外部类的成员, 比如m2()*/
                /*但是如果外部类和局部内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 使用 (外部类名.this.成员) 去访问*/
                /* Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this 就是哪个对象*/
                System.out.println("n1: " + n1 + "  " + "外部类的n1: " + Outer.this.n1);
                m2();
            }
        }
        /*外部类要访问内部类的成员,需要在方法中,创建 Inner02 对象, 然后访问*/
        inner01 inner01 = new inner01();
        inner01.f1();
    }

}
  1. 局部内部类可以直接访问外部类的成员,如果外部类和局部内部类的成员重名时, 默认遵循就近原则, 如果想访问外部类的成员, 使用 (外部类名.this.成员) 去访问。

  2. 外部类要访问内部类的成员,需要在方法中,创建 Inner02 对象, 然后访问。

  3. Outer02.this 本质就是外部类的对象

匿名内部类

匿名内部类的使用

image-20231020150120300

代码

package ch10.innerclass_;

public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}

class Outer04 {
    private int n1 = 10;
    public void method() {

        /*基于接口的匿名内部类*/
        /*
        1. 需求: 想使用 IA 接口,并创建对象
        2. 传统方式, 是写一个类, 实现该接口, 并创建对象
        3. 老韩需求是 Tiger 类只是使用一次, 后面再不使用
        4. 可以使用匿名内部类来简化开发
        5. tiger 的编译类型 ? IA
        6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
        7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址返回tiger
        8. 匿名内部类使用一次, 就不能再使用(是类不能再使用,不是对象)
        */
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤.....");
            }
        };
        //底层会分配类名Outer04$1,相当于
//        class Outer04$1 implements  IA {
//            @Override
//            public void cry() {
//
//            }
//        }
        System.out.println("tiger的运行类型是" + tiger.getClass());
        tiger.cry();
        tiger.cry();

        /*基于类的匿名内部类*/
        //1. father 编译类型 Father
        //2. father 运行类型 Outer04$2
        //3. 底层会创建匿名内部类
        //4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
        //5. 注意("jack") 参数列表会传递给 构造器
        /*
        class Outer04$2 extends Father{
            @Override
            public void test() {
            System.out.println("匿名内部类重写了 test 方法");
            }
        }
*/
        Father father = new Father("jack") {
            @Override
            public void test() {
                System.out.println("匿名内部类重写了test方法");
            }
        };
        System.out.println("fater的运行类型是" + father.getClass());
        father.test();
    }

}

interface IA {
     void cry();
}

//class Tiger implements IA {
//
//    @Override
//    public void cry() {
//
//    }
//}

class Father {
    public Father(String name) { //构造器

    }
    public void test(){}
}

/*
tiger的运行类型是class ch10.innerclass_.Outer04$1
老虎叫唤.....
老虎叫唤.....
fater的运行类型是class ch10.innerclass_.Outer04$2
匿名内部类重写了test方法
*/
匿名内部类注意事项和细节
image-20231020155439639 image-20231020155515631
匿名内部类的最佳实践

将匿名内部类当作实参直接传递,简洁高效。

image-20231020164617721 image-20231020164748290
package ch10.innerclass_;

public class InnerClassExercise02 {
    public static void main(String[] args) {
        CellPhone cellPhone = new CellPhone();
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("起床了....");
            }
        });
        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("上课了...");
            }
        });
    }
}

interface Bell {
    void ring();
}

class CellPhone{

    /*体现了多态*/
    public void alarmClock(Bell bell){
        bell.ring();
    }
}

匿名内部类包含的特点:1. 继承 2.多态 3.动态绑定 4.内部类

成员内部类
image-20231020165833777 image-20231020172154422 image-20231020172203717

代码

package com.hspedu.innerclass;

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();

        //外部其他类,使用成员内部类的三种方式
        //老韩解读
        // 第一种方式
        // outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
        // 这就是一个语法,不要特别的纠结.
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();
        // 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();


    }
}

class Outer08 { //外部类
    private int n1 = 10;
    public String name = "张三";

    private void hi() {
        System.out.println("hi()方法...");
    }

    //1.注意: 成员内部类,是定义在外部内的成员位置上
    //2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        private double sal = 99.8;
        private int n1 = 66;
        public void say() {
            //可以直接访问外部类的所有成员,包含私有的
            //如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
            //,可以通过  外部类名.this.属性 来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
            hi();
        }
    }
    //方法,返回一个Inner08实例
    public Inner08 getInner08Instance(){
        return new Inner08();
    }


    //写方法
    public void t1() {
        //使用成员内部类
        //创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}
静态内部类
image-20231020175412259
main {
    //外部其他类 使用静态内部类
   	//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
	Outer10.Inner10 inner10 = new Outer10.Inner10();
}


class Outer10 {
    private static String name = "张三";
    
    //Inner10就是静态内部类
    //1. 放在外部类的成员位置
    //2. 使用static 修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    //4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    //5. 作用域 :同其他的成员,为整个类体
    static class Inner10 {
        private static String name = "xxx";
        public void say() {
            System.out.println(name + " 外部类name= " + Outer10.name);
        }
    }
}

ch11 枚举和注解

枚举

枚举是一组常量的集合。

可以这里理解: 枚举属于一种特殊的类, 里面只包含一组有限的特定的对象。

枚举的两种实现方式
  1. 自定义类实现枚举
    2) 使用 enum 关键字实现枚举
自定义实现枚举
image-20231024142146918

代码

class Season {
    private String name;
    private String desc;

    public Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }

    public static final Season SPRING = new Season("春天", "温暖");
    public static final Season SUMMER = new Season("夏天", "炎热");
    public static final Season AUTUMN = new Season("秋天", "凉爽");
    public static final Season WINTER = new Season("冬天", "寒冷");

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }
}
  1. 构造器私有化
    2) 本类内部创建一组对象[四个 春夏秋冬]
    3) 对外暴露对象(通过为对象添加 public final static 修饰符)
    4) 可以提供 get 方法, 但是不要提供 set
enum关键字实现枚举
package ch11.enum_;

/**
 * @author czk
 * @date: 2023/10/24
 * @version: 1.0
 */
public class Enumeration02 {
}

enum Season {
    SUMMER("夏天", "炎热"), WINTER("冬天", "寒冷"), SPRINTG();
    private String name;
    private String desc;
    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    
    private Season(){}
}

  1. 当我们使用 enum 关键字开发一个枚举类时, 默认会继承 Enum 类, 而且是一个 final 类
  2. 传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”); 简化成 SPRING(“春天”, “温暖”), 这里必须知道, 它调用的是哪个构造器.
  3. 如果使用无参构造器 创建 枚举对象, 则实参列表和小括号都可以省略
  4. 当有多个枚举对象时, 使用,间隔, 最后有一个分号结尾
  5. 枚举对象必须放在枚举类的行首.
enum常用的方法
  1. toString:Enum 类已经重写过了, 返回的是当前对象名,子类可以重写该方法, 用于返回对象的属性信息
  2. name: 返回当前对象名(常量名) , 子类中不能重写
  3. ordinal: 返回当前对象的位置号, 默认从 0 开始
  4. values: 返回当前枚举类中所有的常量
  5. valueOf: 将字符串转换成枚举对象, 要求字符串必须为已有的常量名, 否则报异常!
  6. compareTo: 比较两个枚举常量, 比较的就是编号! 返回编号相减的数。
enum实现接口
  1. 使用 enum 关键字后, 就不能再继承其它类了, 因为 enum 会隐式继承 Enum, 而 Java 是单继承机制。
  2. 枚举类和普通类一样, 可以实现接口, 如下形式。
    enum 类名 implements 接口 1, 接口 2{}

注解

  1. 注解(Annotation)也被称为元数据(Metadata), 用于修饰解释 包、 类、 方法、 属性、 构造器、 局部变量等数据信息。
  2. 和注释一样, 注解不影响程序逻辑, 但注解可以被编译或运行, 相当于嵌入在代码中的补充信息。
  3. 在 JavaSE 中, 注解的使用目的比较简单, 例如标记过时的功能, 忽略警告等。 在 JavaEE 中注解占据了更重要的角色, 例如用来配置应用程序的任何切面, 代替 java EE 旧版中所遗留的繁冗代码和 XML 配置等。

三个基本的 Annotation:

  1. @Override: 限定某个方法, 是重写父类方法, 该注解只能用于方法
  2. @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
  3. @SuppressWarnings: 抑制编译器警告
Annotation注解

image-20231024161210350

Deprecated注解

image-20231024161650131

SuppressWarnings注解
  1. 当我们不希望看到这些警告的时候, 可以使用 SuppressWarnings 注解来抑制警告信息

  2. 在{“”} 中, 可以写入你希望抑制(不显示)警告信息

SuppressWarnings 作用范围是和你放置的位置相关。

源码的@Target为该注解在哪些地方使用:@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

image-20231024162654373

第十章作业

第一题
image-20231024212728441

注意: 当进行Car c1 = new Car(100);的时候,static String color = "white"不会再执行,c1与c共享color。

ch12 异常

介绍

image-20231026165347136

异常体系图

image-20231026170522274 image-20231026171137272

常见运行时异常

  1. NullPointerException 空指针异常
    2) ArithmeticException 数学运算异常
    3) ArrayIndexOutOfBoundsException 数组下标越界异常
    4) ClassCastException 类型转换异常
    5) NumberFormatException 数字格式不正确异常[]

常见编译异常

编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。

image-20231026173650037

异常处理

image-20231026222408281 image-20231026222425428 image-20231026222439584

try catch使用细节

image-20231026222520997 image-20231026222533495 image-20231026223325158 image-20231026223348997

try-catch执行顺序小结

image-20231028141810597

throws异常处理

image-20231028144138086 image-20231028144155294

throws与throw的区别

image-20231028145150793
/*自定义异常*/
class AgeException extends RuntimeException {
    public AgeException(String message){
        super(message);
    }
}

// main方法中
if(!(age >= 18 && age <= 120)) {
	throw new AgeException("年龄需要在 18~120 之间");
}

例题

image-20231028145132966

注意:finally必须执行。即使捕获到了异常有return语句或抛出异常等语句,也要先执行finally语句,再执行return或抛出异常等。

ch13 常用类

包装类

  1. 针对八种基本数据类型相应的引用类型—包装类
    2) 有了类的特点, 就可以调用类中的方法
image-20231028161608443
包装类的装箱拆箱
image-20231028164442723
// 手动装箱
int n1 = 10;
Integer integer1 = new Integer(n1);
// 手动拆箱
int i = Integer.valueOf(n1);

//jdk5后自动装拆
int n2 = 50;
Integer integer2 = n2;//底层使用的是 Integer.valueOf(n2)
int n3 = integer2; //底层仍然使用的是 intValue()方法
课堂测试题
image-20231028192642691

这是经典面试题: 三元运算符是一个整体,整数1应该转为浮点数1。

Integer面试题
image-20231028194919238

当int数值在-128~127 ,不new,直接返回。已经new好了-128~127的Integer对象在cache数组中。

String类

image-20231028210647606
创建String对象的两种方式
image-20231028210719566 image-20231028210731782
字符串的特性
  1. String是一个final类,代表不可变的字符序列。
  2. 字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的。
题1:
image-20231029114841669

注意是创建了两个对象在常量池中。

题2:
image-20231029115013333

String c = a + b 源码过程:

  • StringBuilder sb = new StringBuilder();
  • sb.append(a); sb.append(b);
  • sb.toString(){return new String(value)}

c是新new出来的对象。

String c = aString a = "hello";
String c = a + b;
System.out.println(c == "helloabc"); //false
String d = "hello" + "abc";
System.out.println(d == "helloabc"); //true 直接看池 d指向常量池
题3:
image-20231029122744394

注意:

  1. change方法中,传进去的是str参数,它指向的是value,而不是指向value所指向的hsp。所以局部变量str变为指向新new变量池中的java。而ex.str仍然指向str开辟的value对象空间,不影响它的值。
  2. ch数组不是在常量池中,是在堆中。ex.ch传入change方法,由于局部变量ch也指向java,则它的修改也会导致ex.ch的修改。

输出:hsp and hava

String的常用方法
image-20231029150937191 image-20231029151007904

StringBuffer类

image-20231029222925538

创建一个StringBuffer对象的源码

// StringBuffer构造器
public StringBuffer() {
    super(16);
}

// 父类
AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
  1. StringBuffer 的直接父类 是 AbstractStringBuilder
  2. StringBuffer 实现了 Serializable, 即 StringBuffer 的对象可以串行化
  3. 在父类中 AbstractStringBuilder 有属性 char[] value,不是 final
  4. StringBuffer 是一个 final 类, 不能被继承
  5. 因为 StringBuffer 字符内容是存在 char[] value, 所有在变化(增加/删除)不用每次都更换地址(即不是每次创建新对象), 所以效率高于 String
image-20231029223037765
StringBuffer常用方法
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏 100true10.5"

//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);

//改
//老韩解读, 使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");

//插
//老韩解读, 在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
StringBuffer与String相互转换
//看 String——>StringBuffe
String str = "hello tom";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象, 对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);

//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
例题:
image-20231029223947514

sb指向"null"字符串

StringBuilder类

image-20231030103217776
String、StringBuffer、StringBuilder比较
image-20231030103302216
三者选择
image-20231030103348648

Arrays类

  • Arrays.toString(arr) 返回数组的字符串格式
  • Arrays.sort
  • Arrays.binarySearch
  • Arrays.copyof 数组元素复制
  • Arrays.fill(num, 99) 使用 99 去填充 num 数组, 可以理解成是替换原理的元素
  • Arrays.asList(2,3,4,5,6,1) 返回的 asList 编译类型 List(接口) asList 运行类型 java.util.Arrays#ArrayList, 是 Arrays 类的 静态内部类

System类

  • exit退出当前程序

  • arraycopy:复制数组元素,比较适合底层调用。一般使用Arrays.copyOf完成复制数组

    int[] src = {1, 2, 3};
    int[] des = new int[3];
    /* 五个参数
    原数组、复制原数组起始位置、目标数组、复制到目标位置起始位置、复制长度
    */
    System.arraycopy(src, 0, des, 0, 3);
    for(int x : des) {
        System.out.println(x);
    }
    
  • currentTimeMillens:返回当时时间距离1970-1-1的毫秒数

  • gc: 运行垃圾回收机制 System.gc();

BigInteger、BigDecimal

// 大数
BigInteger a = new BigInteger("233243244234234214214"); //注意是用字符串传参
BigInteger b = new BigInteger("2314"); //注意是用字符串传参
a.add(b);
a.subtract(b);
a.multiply(b);
a.divide(b);

// 大精度
BigDecimal c = new BigDecimal("1.11111111111244");

Date类(第一代)


//1. 获取当前系统时间
//2. 这里的Date 类是在java.util包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数


//老韩解读
//1. 创建 SimpleDateFormat对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写

SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);

//老韩解读
//1. 可以把一个格式化的String 转成对应的 Date
//2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));

Calendar类(第二代)

//老韩解读
//1. Calendar是一个抽象类, 并且构造器是private
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
                   " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );

前两代的不足:

image-20231030221349553

LocalDate类(第三代)

image-20231030221445091
//第三代日期
//老韩解读
//1. 使用now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);

//2. 使用DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);

System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());

LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒

//提供 plus 和 minus方法可以对当前时间进行加或者减
//看看890天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));

//看看在 3456分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));

本章作业

image-20231030223250095

ch14 集合

集合的框架体系

image-20231031215943746 image-20231031215955534

Collection接口和常用方法

image-20231031220232458

Collection 接口常用方法,以实现子类 ArrayList 来演示.

image-20231031220902913
        List list = new ArrayList();
//        add:添加单个元素
        list.add("jack");
        list.add(10);//list.add(new Integer(10))
        list.add(true);
        System.out.println("list=" + list);
//        remove:删除指定元素
        //list.remove(0);//删除第一个元素
        list.remove(true);//指定删除某个元素
        System.out.println("list=" + list);
//        contains:查找元素是否存在
        System.out.println(list.contains("jack"));//T
//        size:获取元素个数
        System.out.println(list.size());//2
//        isEmpty:判断是否为空
        System.out.println(list.isEmpty());//F
//        clear:清空
        list.clear();
        System.out.println("list=" + list);
//        addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);
//        containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));//T
//        removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2);
        System.out.println("list=" + list);//[聊斋]
//        说明:以ArrayList实现类来演示.
Collection接口遍历(Iterator(迭代器) )
image-20231031221526968 image-20231031221540877
Collection col = new ArrayList();
col.add("fwef");
col.add(234);

Iterator iterator = col.iterator();
// 快捷生成 itit
while (iterator.hasNext()) {
    Object obj = iterator.next();
    System.out.println(obj);
}

如果希望再次遍历,需要重置我们的迭代器

iterator = col.iterator();
Collection接口遍历对象方式for 循环增强

    //        for (Object book : col) {
    //            System.out.println("book=" + book);
    //        }
    for (Object o : col) {
        System.out.println("book=" + o);
    }
  1. 使用增强for, 在Collection集合
  2. 增强for, 底层仍然是迭代器
  3. 增强for可以理解成就是简化版本的 迭代器遍历
  4. 快捷键方式 大写的I

List接口和常用方法

image-20231031223730287

image-20231031223758306
List遍历方式

image-20231101214537430

ArrayList

注意事项
image-20231101215158863
底层操作机制和源码
image-20231101215807128

transient 表示瞬间、短暂的,表示该属性不会被序列化。

ArrayList list = new ArrayList();
for (int i = 1; i <= 10; i++) {
    list.add(i);
}
for (int i = 11; i <= 15; i++) {
    list.add(i);
} 
list.add(100);

image-20231101223346161

image-20231101223359904

注意:Arrays.copyOf()不会覆盖原来的数据,上图用法可以延长列表长度。

Vector

image-20231102215255490 image-20231102215307202

LinkList

介绍
image-20231102221605714 image-20231102221616595
源码
LinkedList list = new LinkedList();
list.add(1)

源码步骤:

  1. public LinkedList() {
    }
    
  2. public boolean add(E e) {
        linkLast(e);
        return true;
    }
    

void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
/*
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
*/
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}


```java
list.remove();

源码步骤:

默认删除首结点

public E remove() {
  return removeFirst();
}


public E removeFirst() {
  final Node<E> f = first;
  if (f == null)
      throw new NoSuchElementException();
  return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
      // assert f == first && f != null;
      final E element = f.item;
      final Node<E> next = f.next;
      f.item = null;
      f.next = null; // help GC
      first = next;
      if (next == null)
          last = null;
      else
          next.prev = null;
      size--;
      modCount++;
      return element;
  }

ArrayList和LinkedList比较
image-20231102221810672

Set接口和常用方法

image-20231102222558022

常用方法和 List 接口一样, Set 接口也是 Collection 的子接口, 因此, 常用方法和 Collection 接口一样.

遍历方式:

image-20231102222629318

HashSet

image-20231102222927516
案例
HashSet set = new HashSet();
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok 因为是两个不同的对象

// 经典面试题
set.add(new String("hsp"));//ok
set.add(new String("hsp"));//加入不了.为什么?
底层机制说明

image-20231103145718356

HashSet hashSet = new HashSet();
hashSet.add("java");
hashSet.add("hsp");
hashSet.add("java");

源码分析:

//1. 执行HashSet()
public HashSet() {
    map = new HashMap<>();
}

//2.执行add()
public boolean add(E e) { //e: "java"
    return map.put(e, PRESENT)==null; //(static) PRESENT = new Object();
}
// 3.执行 put() , 该方法会执行 hash(key) 
public V put(K key, V value) { // key为e, value为PRESENT
    return putVal(hash(key), key, value, false, true);
}

// 4.得到 key 对应的 hash 值, >>>为无符号右移
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//5.执行putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
    // table 是 HashMap 的一个数组变量, 类型是 Node[]
    // if表示当前table是null,或者大小为0
    // 就执行resize()方法第一次扩容,到16个空间
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //(1)根据key得到的hash去计算该 key 应该存放到 table 表的哪个索引位置并把这个位置的对象, 赋给 p
	//(2)判断 p 是否为 null
	//如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT) 就放在该位置 tab[i] = newNode(hash, key, value, null)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null); // (hash值,key,value,next)
    else {
        //一个开发技巧提示: 在需要局部变量(辅助变量)时候,再创建
        Node<K,V> e; K k;
        //如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
		//并且满足 下面两个条件之一:
		//(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
		//(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同。这个equals比较方法遵循动态绑定,可以由程序员制定
		//就不能加入
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 再判断 p 是不 是红黑树
        // 如果是一颗红黑树,就调用 putTreeVal,来进行添加
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).pu tTreeVal(this, tab, hash, key, value);
        else {
            //如果 table 对应索引位置, 已经是一个链表, 就使用 for 循环链表遍历方式比较
			//(1) 依次和该链表的每一个元素比较后, 都不相同, 则接入到该链表的最后
			// 注意在把元素添加到链表后, 立即判断 该链表是否已经达到 8 个结点
			// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
			// 注意, 在转成红黑树时方法里面, 要进行判断, 判断条件
			// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
			//		 resize();
			// 如果上面条件成立, 先 table 扩容.
			// 只有上面条件不成立时, 才进行转成红黑树
			//(2) 依次和该链表的每一个元素比较过程中, 如果有相同情况,就直接 break
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 和上面一样,比较hash值,并判断是否存在相同key或满足key.equal。
                // 若为真,则break,不加入该节点
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 将相同key不同value的对象,进行value替换。
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }  
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
image-20231103175031031

LinkHashSet

image-20231103202201952 image-20231103202215949

Map接口和常用方法

image-20231103211351452
Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put("no1", "张三丰");//当有相同的 k , 就等价于替换.
map.put("no3", "张三丰");//k-v
map.put(null, null); //k-v
map.put(null, "abc"); //等价替换
map.put("no4", null); //k-v
map.put("no5", null); //k-v
map.put(1, "赵敏");//k-v
// 通过 get 方法, 传入 key ,会返回对应的 value
System.out.println(map.get("no2"));//张无忌
image-20231103214649777

注意:KeySet里面的key 和 Values的value始终指向HashMap$Node里面的key和value

Map map = new HashMap();
map.put("no1", "韩顺平");//k-v
map.put("no2", "张无忌");//k-v
map.put(new Car(), new Person());//k-v

//老韩解读
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry, 而一个Entry对象就有k,v
//    EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
//   这时因为 static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 因为 Map.Entry 提供了重要方法
//   K getKey(); V getValue();

Set set = map.entrySet();
System.out.println(set.getClass());// HashMap$EntrySet
for (Object obj : set) { //接受的是Object编译类型

    //System.out.println(obj.getClass()); //HashMap$Node
    //为了从 HashMap$Node 取出k-v

    Map.Entry entry = (Map.Entry) obj; //向下转型 使之能使用Entry接口的方法
    System.out.println(entry.getClass()); //HashMap$Node 运行类型始终是HashMap$Node 
    System.out.println(entry.getKey() + "-" + entry.getValue() );
}



Set set1 = map.keySet(); // 里面保存所有key
System.out.println(set1.getClass());
 
Collection values = map.values(); // 里面保存所有value
System.out.println(values.getClass());
常用方法
image-20231103223611399
遍历方法
//第一组: 先取出 所有的Key , 通过Key 取出对应的Value
Set keyset = map.keySet();
//(1) 增强for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
    System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
    Object key =  iterator.next();
    System.out.println(key + "-" + map.get(key));
}

//第二组: 把所有的values取出
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//(1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
    System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
    Object value =  iterator2.next();
    System.out.println(value);

}

//第三组: 通过EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
for (Object entry : entrySet) {
    //将编译类型为Object的entry 转成 Map.Entry 
    Map.Entry m = (Map.Entry) entry;  
    System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用EntrySet 的 迭代器(第4种)----");
Itera  tor iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
    Object entry =  iterator3.next();
    //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
    //向下转型 Map.Entry
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey() + "-" + m.getValue());
}

注意:为什么不能转成实现了Map.Entry的实现内部类Node呢?

因为HashMap.Node是默认类型,而接口是public类型。

HashMap

image-20231104114546965 image-20231104120103423 image-20231104120112479

扩容、加入元素、创建Map等源码和之前的HashSet相同。

HashTable

image-20231104123418965

简单说明一下Hashtable的底层

  1. 底层有数组 Hashtable$Entry[] 初始化大小为 11
  2. 临界值 threshold 8 = 11 * 0.75
  3. 扩容: 按照自己的扩容机制来进行即可.
  4. 执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
  5. 当 if (count >= threshold) 满足时,就进行扩容 按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
image-20231104123429595

Properties

image-20231104203101995

总结

image-20231104203603242

TreeSet

       1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
       2. 师希望添加的元素,按照字符串大小来排序
       3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类),并指定排序规则

        
        //4. 简单看看源码
        //老韩解读
        /*
        1. 构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator

         public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
         2. 在 调用 treeSet.add("tom"), 在底层会执行到

             if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
                do {
                    parent = t;
                    //动态绑定到我们的匿名内部类(对象)compare
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else //如果相等,即返回0,这个Key就没有加入
                        return t.setValue(value);
                } while (t != null);
            }
         */

//        TreeSet treeSet = new TreeSet();
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //下面 调用String的 compareTo方法进行字符串大小比较
                //如果老韩要求加入的元素,按照长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        //添加数据.
        treeSet.add("jack");
        treeSet.add("tom");//3
        treeSet.add("sp");
        treeSet.add("a");
        treeSet.add("abc");//3


        System.out.println("treeSet=" + treeSet);

TreeMap

使用默认的构造器,创建TreeMap, 是无序的(也没有排序)


        /*
            老韩要求:按照传入的 k(String) 的大小进行排序
         */
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //按照传入的 k(String) 的大小进行排序
                //按照K(String) 的长度大小排序
                //return ((String) o2).compareTo((String) o1);
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("hsp", "韩顺平");//如果构造器中的比较接口是比较长度,则加入不了

        System.out.println("treemap=" + treeMap);

        /*

            老韩解读源码:
            1. 构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
             public TreeMap(Comparator<? super K> comparator) {
                this.comparator = comparator;
            }
            2. 调用put方法
            2.1 第一次添加, 把k-v 封装到 Entry对象,放入root
            Entry<K,V> t = root;
            if (t == null) {
                compare(key, key); // type (and possibly null) check

                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            2.2 以后添加
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do { //遍历所有的key , 给当前key找到适当位置
                    parent = t;
                    cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else  //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
                        return t.setValue(value);
                } while (t   != null);
            }
         */

Collections工具类

image-20231104212602716 image-20231104212611086
Collections.reverse(list);
Collections.sort(list);
Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                //可以加入校验代码.
                return ((String) o2).length() - ((String) o1).length();
            }
        });
Collections.swap(list, 0, 1);
image-20231104214900375

本章作业

image-20231104220452536

5:会抛出异常。因为传入Person类,在源码中需要进行向上转型成Comparable接口类型。可是Person并没有实现该接口。因此会抛出类型转换异常。

解决:

 class Person implements Comparable {
     @Override
     public int compareTo(Object o) {
         // 规则
	}
 }
image-20231104221845991

解释:set.remove(p1)是失败的。因为p1在1号位已经被修改了,调用remove方法的参数为修改后的p1,无法找到原来的p1所hash的位置。

set.add(new Person(1001, “AA”));添加在p1后面。是因为hash值是一样的,但经过比较它们的值并不相同(p1的name修改了),则可以添加在p1的后面。

image-20231104222735313

ch15 泛型

泛型的理解和好处

以前存在的问题:

image-20231104223811917

ArrayList<Dog> arraylist = new ArrayListL<Dog>();

好处:

image-20231105105021120

介绍

image-20231105110117469
//注意,特别强调: E具体的数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型
        Person<String> person = new Person<String>("韩顺平教育");
        person.show(); //String

        /*
            你可以这样理解,上面的Person类
            class Person {
                String s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

                public Person(String s) {//E也可以是参数类型
                    this.s = s;
                }

                public String f() {//返回类型使用E
                    return s;
                }
            }
         */

        Person<Integer> person2 = new Person<Integer>(100);
        person2.show();//Integer

        /*
            class Person {
                Integer s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

                public Person(Integer s) {//E也可以是参数类型
                    this.s = s;
                }

                public Integer f() {//返回类型使用E
                    return s;
                }
            }
         */
    }
}

//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
// 或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
    E s ;//E表示 s的数据类型, 该数据类型在定义Person对象的时候指定,即在编译期间,就确定E是什么类型

    public Person(E s) {//E也可以是参数类型
        this.s = s;
    }

    public E f() {//返回类型使用E
        return s;
    }

    public void show() {
        System.out.println(s.getClass());//显示s的运行类型
    }
}

泛型的语法

image-20231105110430246

image-20231105110440386

泛型的细节

image-20231105111900350
//1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
List<Integer> list = new ArrayList<Integer>(); //OK
//List<int> list2 = new ArrayList<int>();//错误

//2. 说明
//因为 E 指定了 A 类型, 构造器传入了 new A()
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
Pig<A> aPig = new Pig<A>(new A());
aPig.f(); //变量e的运行类型为A
Pig<A> aPig2 = new Pig<A>(new B());
aPig2.f(); //变量e的运行类型为B

//3. 泛型的使用形式
ArrayList<Integer> list1 = new ArrayList<Integer>();
List<Integer> list2 = new ArrayList<Integer>();
//在实际开发中,我们往往简写
//编译器会进行类型推断, 老师推荐使用下面写法
ArrayList<Integer> list3 = new ArrayList<>();
List<Integer> list4 = new ArrayList<>();
ArrayList<Pig> pigs = new ArrayList<>();

// 如果不指定泛型,默认泛型为Object
ArrayList arrayList = new ArrayList(); //等价于ArrayList<Object> arrayList = nwe ArrayList<>();

class A {}
class B extends A {}
class Pig<E> {//
    E e;

    public Pig(E e) {
        this.e = e;
    }

    public void f() {
        System.out.println(e.getClass()); //运行类型
    }
}

自定义泛型类

image-20231105115821994

自定义泛型接口

image-20231105120855873
/**
 *  泛型接口使用的说明
 *  1. 接口中,静态成员也不能使用泛型
 *  2. 泛型接口的类型, 在继承接口或者实现接口时确定
 *  3. 没有指定类型,默认为Object
 */


//实现接口时,直接指定泛型接口的类型
//给U 指定Integer 给 R 指定了 Float
//所以,当我们实现IUsb方法时,会使用Integer替换U, 使用Float替换R
class BB implements IUsb<Integer, Float> {

    @Override
    public Float get(Integer integer) {
        return null;
    }

    @Override
    public void hi(Float aFloat) {

    }

    @Override
    public void run(Float r1, Float r2, Integer u1, Integer u2) {

    }
    

//当我们去实现IA接口时,因为IA在继承IUsu 接口时,指定了U 为String R为Double
//,在实现IUsu接口的方法时,使用String替换U, 是Double替换R
class AA implements IA {

    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {

    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}

//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double> {

}

interface IUsb<U, R> {

    int n = 10;
    //U name; 不能这样使用

    //普通方法中,可以使用接口泛型
    R get(U u);

    void hi(R r);

    void run(R r1, R r2, U u1, U u2);

    //在jdk8 中,可以在接口中,使用默认方法, 也是可以使用泛型
    default R method(U u) {
        return null;
    }
}

自定义泛型方法

image-20231105121932679
public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马", 100);//当调用方法时,传入参数,编译器,就会确定类型
        car.fly(300, 100.1);//当调用方法时,传入参数,编译器,就会确定类型

        //测试
        //T->String, R-> ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(), 11.3f);
    }
}

//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类

    public void run() {//普通方法
    }
    
    //说明 泛型方法
    //1. <T,R> 就是泛型
    //2. 是提供给 fly使用的
    public <T, R> void fly(T t, R r) {//泛型方法
        System.out.println(t.getClass());//String
        System.out.println(r.getClass());//Integer
    }
}

class Fish<T, R> {//泛型类
    public void run() {//普通方法
    }
    
    public<U,M> void eat(U u, M m) {//泛型方法

    }
    
    //1. 下面hi方法不是泛型方法
    //2. 是hi方法使用了类声明的 泛型
    public void hi(T t) {
    }
    
    //泛型方法,既可以使用类声明的泛型,也可以使用自己声明泛型
    public<K> void hello(R r, K k) {
        System.out.println(r.getClass());//ArrayList
        System.out.println(k.getClass());//Float
    }

}

泛型的继承和通配符

image-20231105170952761

//? extends AA 表示 上限, 可以接受 AA 或者 AA 子类
public static void printCollection2(List<? extends AA> c) {}
//? spper AA 表示 下限, 可以接受 AA 或者 AA 父类,不限于直接父类
public static void printCollection3(List<? super AA> c) {}

ch16 坦克大战1

画图

image-20231105174251901 image-20231105174300490 image-20231105223726961 image-20231106102924218

事件处理机制

image-20231106120750069 image-20231106120802221 image-20231106120818121 image-20231106120858279 image-20231106120907656

ch17 多线程基础

线程的应用1-继承Thread

image-20231106161551706 image-20231106163924440
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {

        //创建Cat对象,可以当做线程使用
        Cat cat = new Cat();

        //老韩读源码
        /*
            (1)
            public synchronized void start() {
                start0();
            }
            (2)
            //start0() 是本地方法,是JVM调用, 底层是c/c++实现
            //真正实现多线程的效果, 是start0(), 而不是 run
            private native void start0();

         */

        cat.start();//启动线程-> 最终会执行cat的run方法
  


        //cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
        //说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
        //这时 主线程和子线程是交替执行..
        System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
        for(int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            //让主线程休眠
            Thread.sleep(1000);
        }

    }
}

//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */



class Cat extends Thread {

    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑

        while (true) {
            //该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            //让该线程休眠1秒 ctrl+alt+t
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times == 80) {
                break;//当times 到80, 退出while, 这时线程也就退出..
            }
        }
    }
}
image-20231106165735906

线程的应用2-实现runnable接口

image-20231106220052247 image-20231106220030322

image-20231106220039015

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //dog.start(); 这里不能调用start
        //创建了Thread对象,把 dog对象(实现Runnable),放入Thread
        Thread thread = new Thread(dog);
        thread.start();

//        Tiger tiger = new Tiger();//实现了 Runnable
//        ThreadProxy threadProxy = new ThreadProxy(tiger);
//        threadProxy.start();
    }
}

class Animal {
}

class Tiger extends Animal implements Runnable {

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}

//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy

    private Runnable target = null;//属性,类型是 Runnable

    @Override
    public void run() {
        if (target != null) {
            target.run();//动态绑定(运行类型Tiger)
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start() {
        start0();//这个方法时真正实现多线程方法
    }

    public void start0() {
        run();
    }
}


class Dog implements Runnable { //通过实现Runnable接口,开发线程

    int count = 0;

    @Override
    public void run() { //普通方法
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());

            //休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}

案例:

image-20231106222530978

package ch17;

/**
 * @author czk
 * @date: 2023/11/6
 * @version: 1.0
 */
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        thread1.start();
        thread2.start();
    }
}

class T1 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("hello, world " + (++ count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count == 10) {
                break;
            }
        }
    }
}

class T2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi " + (++ count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count == 5) {
                break;
            }
        }
    }
}

理解

image-20231106222757122 image-20231106222824144

继承Thread和Runnable的接口对比

image-20231106223022025

线程终止

image-20231107220538499

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t1 = new T();
        t1.start();

        //如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
        //让t1 退出run方法,从而终止 t1线程 -> 通知方式

        //让主线程休眠 10 秒,再通知 t1线程退出
        System.out.println("main线程休眠10s...");
        Thread.sleep(10 * 1000);
        t1.setLoop(false);
    }
}

class T extends Thread {
    private int count = 0;
    //设置一个控制变量
    private boolean loop = true;
    @Override
    public void run() {
        while (loop) {

            try { 
                Thread.sleep(50);// 让当前线程休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T 运行中...." + (++count));
        }

    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程方法

image-20231107222341828 image-20231107222413007
package ch17;

/**
 * @author czk
 * @date: 2023/11/7
 * @version: 1.0
 */
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("老韩"); //设置线程名称
        t.setPriority(Thread.MIN_PRIORITY); //设置优先级
        t.start();

        //主线程打印5hi,然后就中断子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + i);
        }

        System.out.println(t.getName() + " 线程优先级 = " + t.getPriority());
        t.interrupt();
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "运行中...");
            }

            System.out.println(Thread.currentThread().getName() + "休眠中...");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                //当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
                //InterruptedException 是捕获到一个中断异常.
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

image-20231107223408460

T2 t2 = new T2();
t2.start();

for(int i = 1; i <= 20; i++) {
    Thread.sleep(1000);
    System.out.println("主线程(小弟) 吃了 " + i  + " 包子");
    if(i == 5) {
        System.out.println("主线程(小弟) 让 子线程(老大) 先吃");
        //join, 线程插队
        //t2.join();// 这里相当于让t2 线程先执行完毕
        Thread.yield();//礼让,不一定成功..
        System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃..");
    }

}

用户线程和守护线程

image-20231108085519100

MyDemoThread dt = new MyDemoThread();
// 将dt设置为守护线程,当所有线程结束后,dt也就自动结束
dt.setDaemon(true);
dt.start();

线程生命周期

image-20231108091856588
T t = new T();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
    System.out.println(t.getName() + " 状态 " + t.getState());
    Thread.sleep(500);
} 
System.out.println(t.getName() + " 状态 " + t.getState());

线程同步Synchronized

image-20231108102715498 image-20231108102724757

互斥锁

image-20231108104618053
  • 方法加锁
  • 代码块加锁

//同步方法(静态的) 的锁为当前类本身
//老韩解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
//2. 如果在静态方法中, 实现一个同步代码块.
/*
synchronized (SellTicket03.class) {
	System.out.println("m2");
}
*/

Oject object = new Objcet();
//1. public synchronized void sell() {} 就是一个同步方法
//2. 这时锁在 this 对象
//3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在 this 对象
public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行 sell 方法

    synchronized (/*this*/ object) { //this或object相当于一个互斥锁,则下面代码是同步代码块,this或同一个对象。 this也必须是指的同一个对象,不能是多个new出来的this,就没用了
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop = false;
            return;
        }
    }

public synchronized static void m1() { //静态同步方法
} 


public static void m2() {
    synchronized (SellTicket03.class) { //互斥锁,下面为静态方法中的同步代码块 synchronized(类.class)
        System.out.println("m2");
    }
}

注意事项:

image-20231108105338570

死锁

image-20231108110847643

class DeadLockDemo extends Thread {
    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Overrid
    public void run() {

        //下面业务逻辑的分析
        //1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁, 下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) { 
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
                
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) { 
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}

释放锁

image-20231108111526704

image-20231108111539139

作业

public class Homework02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("t1");
        Thread thread2 = new Thread(t);
        thread2.setName("t2");
        thread1.start();
        thread2.start();
    }
}

//编程取款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2. 每次取出 1000
class T implements  Runnable {
    private int money = 10000;

    @Override
    public void run() {
        while (true) {

            //解读
            //1. 这里使用 synchronized 实现了线程同步
            //2. 当多个线程执行到这里时,就会去争夺 this对象锁
            //3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
            //4. 争夺不到this对象锁,就blocked ,准备继续争夺
            //5. this对象锁是非公平锁.

            synchronized (this) {//
                //判断余额是否够
                if (money < 1000) {
                    System.out.println("余额不足");
                    break;
                }

                money -= 1000;
                System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
            }

            //休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

ch19 IO流

文件流的概念:

image-20231115120220289

常用文件相关操作

创建文件
image-20231115190952168
@Test
    public void create1() {
        String filePath = "D:\\new1.txt";
        // 创建文件对象
        File file = new File(filePath);
        try {
            file.createNewFile(); // 创建新的文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create2 (){
        File parentFile = new File("d:\\");
        String filename = "news2.txt";
        File file = new File(parentFile, filename);
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void create3() {
        String parentPath = "d:\\";
        String filename = "news3.txt";
        File file = new File(parentPath, filename);
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
获取文件相关信息

image-20231115191719874

image-20231115192728782

IO流体系

image-20231115194131647 image-20231115194141788 image-20231115194153104

image-20231115194222984

image-20231115194610631

FileInputStream

image-20231115194342617
String filePath = "D:\\new1.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
    fileInputStream = new FileInputStream(filePath);
    // 从该输入流读取一个字节的数据。
    // 如果返回-1,表示读取完毕
    while ((readData = fileInputStream.read()) != -1) {
        System.out.print((char) readData);  
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    // 关闭文件流,释放资源
    try {
        fileInputStream.close();
    } catch (IOException e  )  {
        e.printStackTrace();
    }
}

// 一次性发读取多个字节
byte[] buf = new byte[4];
while ((readLen = fileInputStream.read(buf)) != -1) {
    System.out.print(new String(buf, 0, readLen));
}

FileOutputStream

  1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
  2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
    //得到 FileOutputStream对象 对象
    //老师说明
   
    fileOutputStream = new FileOutputStream(filePath, true);
    //写入一个字节
    //fileOutputStream.write('H');//
    //写入字符串
    String str = "hsp,world!";
    //str.getBytes() 可以把 字符串-> 字节数组
    //fileOutputStream.write(str.getBytes());
    /*
            write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流
             */
    fileOutputStream.write(str.getBytes(), 0, 3);

} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

FileReader、FileWriter

image-20231115205939888 image-20231115205947811 image-20231115210056760

关闭或刷新之前,所写的内容还在内存里面,只有关闭和刷新才能写到指定的文件中去。

节点流和处理流

image-20231116104831963 image-20231116104844268

节点流:针对特定数据源,灵活性较差。

image-20231116105258479

处理流(包装流):可以封装一个节点流,灵活性高。操作时真正调用的还是节点流,只是封装了它。

image-20231116105212113

节点流和处理流的区别和联系

image-20231116114728027 image-20231116114745167

BufferedReader

public static void main(String[] args) throws Exception {
        String filePath = "d:\\new1.txt";
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
        String line;
        while((line = bufferedReader.readLine()) != null) {
            System.out.println(line);
        }
        bufferedReader.close();
    }

对象流

也是处理流的一种。

image-20231116223736061

image-20231116223746040
介绍:

功能: 提供了对基本类型或对象类型的序列化和反序列化的方法
ObjectOutputStream 提供 序列化功能
ObjectInputStream 提供 反序列化功能

image-20231116223809721

ObjectOutputStream\
// 来自某个包的对象,必须实现序列化接口,才能进行序列化和反序列
class Dog implements Serializable {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
String filePath = "e:\\data.dat";

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String
//保存一个dog对象
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");
ObjectInputStream
//指定反序列化的文件
String filePath = "e:\\data.dat";

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的 顺序 一致
//2. 否则会出现异常

System.out.println(ois.readInt());
System.out.println(ois.readBoolean());

System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());


//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog

//这里是特别重要的细节:
//1. 如果我们希望调用Dog的方法, 需要向下转型
//2. 需要我们将Dog类的定义,放在到可以引用的位置
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //旺财..

//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();

细节:

image-20231117153947305

标准输入流和标准输出流

image-20231117154602837

//System 类 的 public final static InputStream in = null;
// System.in 编译类型   InputStream
// System.in 运行类型   BufferedInputStream
// 表示的是标准输入 键盘
System.out.println(System.in.getClass());

//老韩解读
//1. System.out public final static PrintStream out = null;
//2. 编译类型 PrintStream
//3. 运行类型 PrintStream
//4. 表示标准输出 显示器
System.out.println(System.out.getClass());]
 
System.out.println("hello, 韩顺平教育~");

Scanner scanner = new Scanner(System.in);
System.out.println("输入内容");
String next = scanner.next();
System.out.println("next=" + next);

转换流

InputStreamReader和OutputStreamWriter

image-20231117161011808 image-20231117161055324 image-20231117160738137

InputStreamReader示例代码:

String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);

//将2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(
    new FileInputStream(filePath), "gbk"));

//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();

打印流

打印流只有输出流,没有输入流。

image-20231117162946115
PrintStream
PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
/*
             public void print(String s) {
                if (s == null) {
                    s = "null";
                }
                write(s);
            }

         */
out.print("john, hello");
//因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出
out.write("韩顺平,你好".getBytes());
out.close();

//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
//        checkIO();
//        setOut0(out); // native 方法,修改了out
//   }
System.setOut(new PrintStream("e:\\f1.txt"));
System.out.println("hello, 韩顺平教育~");

printWriter
//PrintWriter printWriter = new PrintWriter(System.out);
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 北京你好~~~~");
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..

Properties类

image-20231117165926027

image-20231117171227764

String filePath = "\\src\\ch19\\mysql.properties";
Properties properties = new Properties();
properties.load(new FileReader("src/ch19/mysql.properties"));

// 打印到显示器
properties.list(System.out);
String user = properties.getProperty("user");
System.out.println(user);
String pwd = properties.getProperty("pwd");
System.out.println(pwd);
properties.setProperties("charset", "utf-8");
properties.setProperties("user", "汤姆");

// null参数是注释内容,会保存到文件最上方,用//注释
properties.store(new FileOutputStream(new FileOutputStream("\\src\\ch19\\mysql.properties"), null))

ch21 网络编程

概念

image-20231118171621967 image-20231118171634246 image-20231118171643516 image-20231118171706298 image-20231118171713393 image-20231118171721493 image-20231118190122621 image-20231118190229905
TCP和UDP
image-20231118190218479

InetAddress类

image-20231118194126377
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
InetAddress host1 = InetAddress.getByName("DESKTOP-AU9M7GV");
System.out.println(host1);

InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2);
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress);
System.out.println(host2.getHostName());

/*
DESKTOP-AU9M7GV/10.163.166.121
DESKTOP-AU9M7GV/10.163.166.121
www.baidu.com/36.155.132.55
36.155.132.55
www.baidu.com
*/

Socket

image-20231118195106920 image-20231118195209496

TCP网络通信编程

image-20231119145903297
应用案例
image-20231118201014679 image-20231118213231860

思路分析:

image-20231118214046744
//SocketTCP01Server.java
public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        // 细节:这个ServerSocket 可以通过 accept() 返回多个Socket (多个客户端连接服务器的并发)
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端,在9999端口监听,等待连接");
        Socket socket = serverSocket.accept();

        System.out.println("服务端 socket = " + socket.getClass());

        InputStream is = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = is.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }

        OutputStream os = socket.getOutputStream();
        os.write("hello client".getBytes());
        socket.shutdownOutput(); // 设发送数据结束标志

        is.close();
        socket.close();
        serverSocket.close();
    }
}

public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        // 连接本机的 9999 端口
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端socket =  " + socket.getClass());

        OutputStream os = socket.getOutputStream();
        os.write("hello Server!".getBytes());
        socket.shutdownOutput();// 设置结束标志

        InputStream is = socket.getInputStream(); 
        byte[] bytes = new byte[1024];
        int readLen;
        while((readLen = is.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, readLen));
        }
 
        os.close();
        socket.close();
        System.out.println("客户端退出了");
    }
}
netstat指令
image-20231119150002399

UDP网络通信编程

image-20231119153823973 image-20231119153835480
应用案例
image-20231119154148362
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(9999);
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);
        System.out.println("接收端A等待接受数据");
        socket.receive(packet);
        int length = packet.getLength();
        byte[] data = packet.getData();
        String s = new String(data, 0, length);
        System.out.println(s);

        data = "好的,明天见".getBytes();
        packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.163.166.121"), 9998);
        socket.send(packet);

        socket.close();
        System.out.println("A端退出");
    }
}


public class UDPSenderB {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(9998);
        byte[] data = "明天整火锅".getBytes();
        DatagramPacket packet =
            new DatagramPacket(data, data.length, InetAddress.getByName("10.163.166.121"), 9999);
        socket.send(packet);

        byte[] buf = new byte[1024];
        packet = new DatagramPacket(buf, buf.length);
        socket.receive(packet);
        int len = packet.getLength();
        data = packet.getData();
        String s = new String(data, 0, len);
        System.out.println(s);

        socket.close();
        System.out.println("B端退出");
    }
}

ch23 反射

引出需求

image-20231119212015196
//re.reporterties 配置文件
classfullpath=ch23.Cat
method=hi
//根据配置文件 re.properties 指定信息, 创建 Cat 对象并调用方法 hi
Properties properties = new Properties();
properties.load(new FileInputStream("src/ch23/re.reporterties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
System.out.println(classfullpath);
System.out.println(methodName);

//创建对象 , 传统的方法, 行不通 =》 反射机制
//new classfullpath();
//使用反射机制解决
//加载类, 返回 Class 类型的对象 cls
Class cls = Class.forName(classfullpath);
//通过 cls 得到你加载的类 ch23.CatCat 的对象实例
Object o = cls.newInstance();

//可以通过向下转型调用方法
//Cat cat = (Cat) o;
//cat.say();

//通过 cls 得到你加载的类 ch23.Cat 的 methodName"hi" 的方法对象
// 即: 在反射中, 可以把方法视为对象(万物皆对象)
Method method = cls.getMethod(methodName);
// 通过 method1 调用方法: 即通过方法对象来实现调用方法
method.invoke(o); // 传统方法 对象.方法() , 反射机制 方法.invoke(对象)

反射机制

image-20231119223416878 image-20231119223428185
Field nameField = cls.getField("name");
System.out.println(nameField.get(o));

Constructor constructor1 = cls.getConstructor();
System.out.println(constructor1);
Constructor constructor2 = cls.getConstructor(String.class);
System.out.println(constructor2);

/*
tom
public ch23.Cat()
public ch23.Cat(java.lang.String)
*/

反射优缺点

image-20231120111321181

image-20231120111339971

Class类

image-20231120112950476 image-20231120113000535
Class类常用方法:
image-20231120115926161
//1 . 获取到 Car 类 对应的 Class 对象
//<?> 表示不确定的 Java 类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出 cls
System.out.println(cls); //显示 cls 对象, 是哪个类的 Class 对象 com.hspedu.Car
System.out.println(cls.getClass());//输出 cls 运行类型 java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//包名
//4. 得到全类名
System.out.println(cls.getName());
//5. 通过 cls 创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car);//car.toString()
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f : fields) {
    System.out.println(f.getName());//名称
}
获取Class类对象
image-20231120121940794 image-20231120121948859 image-20231120122001025
哪些类型有Class对象
image-20231120122434752
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void 数据类型
Class<Class> cls9 = Class.class;//

/*
class java.lang.String
interface java.io.Serializable
class [Ljava.lang.Integer;
class [[F
interface java.lang.Deprecated
class java.lang.Thread$State
long
void
class java.lang.Class
*/

类加载

image-20231120154713580

类加载时机:

image-20231120154734044

类加载过程图:

image-20231120162700284 image-20231120162722556
加载阶段:
image-20231120162747317
连接阶段:
  • 验证
image-20231120162811815
  • 准备:

    image-20231120162838367
//1. n1 是实例属性, 不是静态变量, 因此在准备阶段, 是不会分配内存
//2. n2 是静态变量, 分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
  • 解析:

    image-20231120163324517
初始化阶段
image-20231120164650575
public static void main(String[] args) throws ClassNotFoundException {
    //1. 加载B类,并生成 B的class对象
    //2. 链接 num = 0
    //3. 初始化阶段
    //    依次按顺序自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并合并
    /*
                clinit() { //按顺序收集
                    System.out.println("B 静态代码块被执行");
                    //num = 300; //被合并了 
                    num = 100;
                }
                合并: num = 100

         */

    //new B();//类加载
    //System.out.println(B.num);//100, 如果直接使用类的静态属性,也会导致类的加载

    //加载类的时候,是有同步机制控制
    /*
        protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
        {
            //正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
            synchronized (getClassLoadingLock(name)) {
            //....
            }
            }
         */
    B b = new B();
}

class B {
    static {
        System.out.println("B 静态代码块被执行");
        num = 300;
    }

    static int num = 100;

    public B() {//构造器
        System.out.println("B() 构造器被执行");
    }
}

通过反射获取类的结构信息

Class类
image-20231120171546553
Filed类
image-20231120172818082
Method类
image-20231120172914057
Constructor 类
image-20231120173204620

通过反射创建对象

image-20231120174612021
//1. 先获取到 User 类的 Class 对象
Class<?> userClass = Class.forName("com.hspedu.reflection.User");
//2. 通过 public 的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);

//3. 通过 public 的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public 的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例, 并传入实参
Object hsp = constructor.newInstance("hsp");
System.out.println("hsp=" + hsp);

//4. 通过非 public 的有参构造器创建实例
//4.1 得到 private 的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问 private 构造器/方法/属性, 反射面前, 都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);

class User { //User 类
    private int age = 10;
    private String name = "韩顺平教育";
    public User() {//无参 public
    } 
    public User(String name) {//public 的有参构造器
        this.name = name;
    } 
    private User(int age, String name) {//private 有参构造器
        this.age = age;
        this.name = name;
    }
    public String toString() {
        return "User [age=" + age + ", name=" + name + "]";
    }
}

通过反射访问类中的成员

image-20231120215900392

勘误:(clazz -> class)

//1. 得到 Student 类对应的 Class 对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是 Student
System.out.println(o.getClass());//Student

//3. 使用反射得到 age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);//
System.out.println(age.get(o));//返回 age 属性的值

//4. 使用反射操作 name 属性
Field name = stuClass.getDeclaredField("name");
//对 name 进行暴破, 可以操作 private 属性
name.setAccessible(true);
//name.set(o, "老韩");
name.set(null, "老韩~");//因为 name 是 static 属性, 因此 o 也可以写出 null
System.out.println(o);
System.out.println(name.get(o)); //获取属性值
System.out.println(name.get(null));//获取属性值, 要求 name 是 static


class Student {//类
    public int age;
    private static String name;
}
image-20231120221112184

在反射中, 如果方法有返回值, 统一返回 Object , 但是他运行类型和方法定义的返回类型一致。

作业

image-20231120222239899
/**
         * 利用Class类的forName方法得到File类的class 对象
         * 在控制台打印File类的所有构造器
         * 通过newInstance的方法创建File对象,并创建E:\mynew.txt文件
         */
//1. Class类的forName方法得到File类的class 对象
Class<?> fileCls = Class.forName("java.io.File");
//2. 得到所有的构造器
Constructor<?>[] declaredConstructors = fileCls.getDeclaredConstructors();
//遍历输出
for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println("File构造器=" + declaredConstructor);
}
//3. 指定的得到 public java.io.File(java.lang.String)
Constructor<?> declaredConstructor = fileCls.getDeclaredConstructor(String.class);
String fileAllPath = "e:\\mynew.txt";
Object file = declaredConstructor.newInstance(fileAllPath);//创建File对象

//4. 得到createNewFile 的方法对象
Method createNewFile = fileCls.getMethod("createNewFile");
createNewFile.invoke(file);//创建文件,调用的是 createNewFile
//file的运行类型就是File
System.out.println(file.getClass());
System.out.println("创建文件成功" + fileAllPath);

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值