【Java SE基础回顾】看这篇就够了!

JavaSE复习

参考视频:【狂神说Java】JavaSE阶段回顾总结

简单的就不讲了,比如什么break和continue区别,甚至一些什么什么继承封装多态的概念等等。 主要写一些Java特有的,重点的基础知识。主要还是例子和代码更多,更有利于理解。

java SE、 java ME、 java EE

  • Java SE (Java Standard Edition):标准版
  • Java EE (Java Enterprise Edition):企业版
  • Java ME (Java Micro Edition):微型版

JDK?JRE?

JDK (Java Development Kit)软件开发工具包

JRE(Java Runtime Environment)运行环境

如果用户只需要运行,不需要开发,那只安装JRE即可。

复习helloworld

要求做到打开记事本能手写,并用javac编译java运行。

package com.zy;

public class hello {
    public static void main(String[] args) {
        System.out.println("hello world!");
    }
}

数据类型

在这里插入图片描述

注意C++中char是1个字节的,java中char是2个字节(java内部都是用unicode的,所以java中的char是2个字节),C++中char应该对应byte

扩展,ASCII,Unicode,utf-8的区别

Unicode是字符集,UTF-8是编码规则

<< >> 左移,右移运算符

<<< >>> 无符号左移,右移运算符,高位补0

求-15>>2 和 -15>>>2:-4和1073741820

解释:一个是带符号右移两位,一个是无符号右移两位。

java包机制

域名倒写:规范写法: 公司域名倒着写+模块名字 com.nuc.maneger

例子:比如 com.baidu.video,因为java内部实际上是以文件夹形式存在的,是按com,baidu,video依次生成文件夹的
具体功能的是子文件夹,所以要倒着写。

在这里插入图片描述

package:无论什么时候都在第一行

为了更好地组织类,Java提供了包机制,用于区别类名的命名空间。

  1. 包名与目录结构package 声明的包名需要与文件所在的目录结构严格对应。例如,假设有以下包声明:

    package com.example.myproject;
    

    该文件应该位于以下路径中:

    /path/to/project/com/example/myproject/YourClass.java
    
  2. 默认包:如果你没有指定 package,类会被放入一个默认包中。这时文件可以直接放在项目的根目录下。但在实际开发中,不推荐将类放在默认包中,因为这样会造成组织混乱和潜在的类名冲突。

一个文件只能有一个public类,且必须与文件名一直(大小写也要匹配)

javaDoc:通过注释生成程序帮助文档。

JDK帮助文档就是通过javaDoc生成的。

常见格式

/**
 * Method description.
 * @return description of return value
 */
public int methodName() {}

可选注释参数:

@author:标签用于指定作者信息
@param:用于标记方法的参数说明。
@return:用于标记方法的返回值说明。
@throws 或 @exception:用于标记方法可能抛出的异常说明。
@see:用于指定参考文档的链接。
@since:用于指定API的版本信息。
@deprecated:用于标记已过时的API。
@inheritDoc:用于继承父类或接口的文档注释。
@link:类似于@see,用于创建链接到其他文档的标签。

javadoc

在这里插入图片描述

encoding指定用于读取源文件的字符编码。
charset 指定生成的 HTML 文档使用的字符集编码

Scanner对象输入文本

package com.zy;

import java.util.Scanner;

public class hello {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);   // 创建Scanner对象

        System.out.println("输入文本:");
        String userInput = scanner.nextLine();  // 读取用户输入的一行文本

        System.out.println("你输入了:" + userInput);

        scanner.close();
    }
}

foreach的使用

package com.zy;

public class hello{
    public static void main(String[] args) {
        int[] numbers = {1,2,3,4,5};
        for(int num : numbers) {
            System.out.println(num);
        }
    }
}

java中带标签的break和continue

package com.zy;


public class hello{
    public static void main(String[] args) {
        outLoop:for(int i=0; i<10;i++) {	// 带个标签outLoop
            for(int j=0;j<5;j++) {
                System.out.printf("%d,%d\n",i,j);
                break outLoop;	// 直接跳出最外层循环。
            }
        }
    }
}

java可变长参数

package com.zy;

class People {
    int a;
}

public class hello{
    public static void test(Integer number,String...name) {
        System.out.println(number.getClass());
        System.out.println(name.getClass());
        System.out.println(number);
        for(String s : name) {
            System.out.println(s);
        }

    }

    public static void main(String[] args) {
        test(3, "lilin","hali");
    }
}

输出

class java.lang.Integer
class [Ljava.lang.String;	//[表示一维数组,L表示是String类型。(如果是二位数组则为[[)
3
lilin
hali

Arrays工具类

在java中,数组本身没有方法可以调用,于是在API中提供了工具类Arrays供我们使用。

binarySearch():在已排序的数组中查找指定元素的索引。
copyOf():将一个数组的部分或全部元素复制到一个新数组中。
copyOfRange():将一个数组的指定范围内的元素复制到一个新数组中。

1.toString():将数组转换为一个字符串,便于打印和调试。

        int[] ints = {1,2,3,4};
        System.out.println(ints);   // 直接打印地址:[I@1540e19d
        System.out.println(Arrays.toString(ints));  // [1, 2, 3, 4]
        System.out.println(Arrays.toString(ints).charAt(0));	// [

2.fill():将数组的某个范围的所有元素都设置为指定值。

        int[] arr = new int[5];
        Arrays.fill(arr,1,3,4);	//左开右闭
        System.out.println(Arrays.toString(arr));   // [0, 4, 4, 0, 0]
    

3.sort(数组)

        int[] arr = {4,2,6,3,8};
        System.out.println("升序前:"+Arrays.toString(arr));    // 升序前:[4, 2, 6, 3, 8]
        Arrays.sort(arr);
        System.out.println("升序后:"+Arrays.toString(arr));    // 升序后:[2, 3, 4, 6, 8]

4.sort(数组,排序规则)

在 Java 中,对于 基本类型(如 int[]),Arrays.sort() 只能进行升序排序。如果要实现降序排序,你需要先将数组转换为包装类型,如 Integer[],然后使用自定义的比较器 (Comparator) 进行降序排列。

        int[] array = {1, 3, 2, 5, 4};
        // 1.将int[]转换为Integer[]
        Integer[] arrInteger = Arrays.stream(array).boxed().toArray(Integer[]::new);
        // 2.使用 Arrays.sort() 和 Collections.reverseOrder() 进行降序排序
        Arrays.sort(arrInteger,Collections.reverseOrder());
        // 3.打印排序后的结果
        System.out.println(Arrays.toString(arrInteger));

或者是,先升序排序,然后将数组中元素反转。

        int[] array = {1, 3, 2, 5, 4};
        Arrays.sort(array);
        for(int i=0;i<array.length/2;i++) {
            int tmp = array[i];
            array[i] = array[array.length-i-1];
            array[array.length-i-1] = tmp;
        }
        System.out.println(Arrays.toString(array));

如果我想对自定义的类型数组进行排序呢?(比如:Student类的数组),这里给出两种方法:

1.实现Comparable接口,完成自定义排序

package com.zy;

import java.util.Arrays;
import java.util.Collections;

// 定义 Student 类,并实现 Comparable 接口
class Student implements Comparable<Student> {
    String name;
    int age;

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

    @Override
    public int compareTo(Student other) {
        return this.age - other.age;
    }

    @Override
    public String toString() {
        return name + ":" + age;
    }
}


public class hello {
    public static void main(String[] args) {
        Student[] students = {
                new Student("Alice",22),
                new Student("Bob",18),
                new Student("Charlie",20),
        };
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

上面方式看起来不太简洁,需要修改类本身。更灵活的方式如下:

2.利用匿名Comparator 对象,灵活地定义多种不同的排序规则。

package com.zy;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

class Student{
    String name;
    int age;

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

    @Override
    public String toString() {
        return name + ":" + age;
    }
}


public class hello {
    public static void main(String[] args) {
        Student[] students = {
                new Student("Alice",22),
                new Student("Bob",18),
                new Student("Charlie",20),
        };
        // 按年龄排序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.age - o2.age;
            }
        });
        System.out.println(Arrays.toString(students));

        // 按姓名排序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.name.compareTo(o2.name);
            }
        });
        System.out.println(Arrays.toString(students));

    }
}

5.copyOfRange(original,from,to)

original:第一个参数为要拷贝的数组对象
from:第二个参数为拷贝的开始位置(包含)
to:第三个参数为拷贝的结束位置(不包含)

继承,封装,多态

写一遍经典的Animal继承,体会一下Java面向对象三大特性。

package com.zy;

class Animal {
    private String name;
    private int age;

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

    // 封装:getter 和 setter 方法
    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 void describe() {
        System.out.println("This is an animal named" + name + ", age" + age);
    }
}

// 子类Dog继承父类Animal
class Dog extends Animal {
    private String breed;

    // 子类构造函数,调用父类的构造函数
    public Dog(String name, int age, String breed) {
        super(name, age);       // 继承:调用父类构造函数
        this.breed = breed;
    }

    public String getBreed() {
        return breed;
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    @Override
    public void describe() {
        super.describe();   // 调用父类describe方法
        System.out.println("This is a " + breed + "dog.");
    }

    // Dog 类特有方法
    public void bark() {
        System.out.println("woof woof!");
    }
}


public class Hello {
    public static void main(String[] args) {
        Animal animal = new Animal("Generic Animal", 5);
        animal.describe();  // 调用 Animal 类的方法

        Dog dog = new Dog("Buddy",3,"Golden Retriever");
        dog.describe(); // 调用重写后的describe方法
        dog.bark();     // 调用Dog类的bark方法

        // 多态体现
        Animal animalDog = new Dog("Max",2,"Beagle");
        animalDog.describe();   // 调用的是子类Dog的describe方法
//        animalDog.bark();   // 错误,Animal没有bark()

        // 检查对象类型,使用instance of
        if(animalDog instanceof Dog) {
            Dog specificDog = (Dog)animalDog; //向下转型
            specificDog.bark();
        }
    }
}

output:

This is an animal named Generic Animal, age 5
This is an animal named Buddy, age 3
This is a Golden Retriever dog.
Woof Woof!
This is an animal named Max, age 2
This is a Beagle dog.
Woof Woof!
The dog's new name is: Charlie

等等,既然animalDog instanceof Dog为真,也就是animalDog 就是Dog类型,那为什么还需要向下转型(Dog)animalDog??

如果有这种疑问,说明有两个问题:1. 对instanceof不熟悉;2.对向上转型和向下转型不熟悉。

instanceof

instanceof 是 Java 中的一个关键字,用于判断一个对象是否是某个类或其子类的实例

举个例子

现在有类的继承链如下:
Object > Person > Teacher
Object > Person > Student
Object > String
Object obj = new Student();
obj instanceof Student;	// true
obj instanceof Person;	// true
obj instanceof Object;	// true
obj instanceof String;	// false
obj instanceof Teacher;	// false

Object obj = new Person();
obj instanceof Student;	// false
obj instanceof Teacher; // false

Person person = new Student();
person instanceof Student;	// true
person instanceof Person;	// true;
person instanceof Object; // true;
person instanceof String;	// 编译错误
person instanceof Teacher; // false

// null用instanceof跟任何类型比较时都是false
null instanceof Object; // false

所以if(animalDog instanceof Dog)是判断animalDog 是否为Dog类型或其子类。

关于java中向上转型和向下转型

向上转型(Upcasting):子类对象转换为父类类型,把子当父用。向上转型通常是隐式进行的,意味着你不需要手动指定转换操作。

        Dog dog = new Dog();
        Animal animal = dog;  // 向上转型,隐式进行
        animal.makeSound();   // 输出 "Bark",即调用子类的重写方法

向上转型是安全的,不会发生 ClassCastException

访问限制:向上转型后,编译器只能识别父类的成员(属性和方法),不能访问子类特有的方法或属性。

向下转型(Downcasting):向下转型是指将父类类型的引用转换为子类类型。这种转换是显式进行的,需要手动指定转换操作,并且存在一定的风险,向下转型时,必须确保父类对象实际上是该子类的实例,否则会抛出 ClassCastException

Animal animalDog = new Dog("Max",2,"Beagle");
if(animalDog instanceof Dog) {
            Dog specificDog = (Dog)animalDog; //向下转型
            specificDog.bark();
        }

向下转型之前,通常使用 instanceof 关键字检查对象是否属于某个子类,以防止 ClassCastException 异常。

接口interface

注意,java是单继承,只能继承一个父类,但是可以实现多个接口。

记得本科时候Java老师说过,类描述一个实体,接口描述一组动作。

接口是 Java 中的一种引用类型,它类似于一个契约或协议,只能定义方法的签名(方法名、返回类型和参数),不能包含方法的具体实现。

class Animal {
    protected String name;

    Animal(String name) {
        this.name = name;
    }
}

interface IFlying {
    void fly();
}

interface IRuning {
    void run();
}

interface ISwimming {
    void swim();
}

// 猫会跑
class Cat extends Animal implements IRuning{
    Cat(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "is running");
    }
}

// 鱼会游泳
class Fish extends Animal implements ISwimming {
    Fish(String name) {
        super(name);
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is swimming");
    }
}

// 狗既能跑,又能游泳
class Dog extends Animal implements IRuning, ISwimming {
    Dog(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "is running");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is swimming");
    }
}

// 鸭子 水陆空三栖
class Duck extends Animal implements IRuning,ISwimming,IFlying {
    Duck(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(this.name + "is running");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "is swimming");
    }

    @Override
    public void fly() {
        System.out.println(this.name + "is flying");
    }
}

default 方法

  • Java 8 引入了 default 方法,允许在接口中提供默认方法实现。这是为了在不破坏现有实现的情况下扩展接口。default 方法必须使用 public 访问级别,因为接口中的所有方法默认都是 public 的(即使不显式声明)
public interface MyInterface {
    default void doSomething() {
        System.out.println("Default implementation");
    }
}

java接口实际上是可以定义变量的,但是他们有一下特性:

隐式 public static final 修饰:接口中的变量默认是 publicstaticfinal

public:意味着接口中的变量是公开的,可以在任何地方访问。

static:意味着变量属于接口,而不是某个具体的实现类。可以通过接口名直接访问。

final:意味着变量是常量,一旦赋值就不能修改

所以,接口中的变量实际上是常量,用于共享和定义全局的常量值。

必须初始化:由于接口中的变量是 final,所以必须在定义时进行初始化。

interface MyInterface {
    int NUM = 1000;

    void doSomething();
}

public class Hello implements MyInterface{

    @Override
    public void doSomething() {
        System.out.println("NUM:" + NUM);
    }

    public static void main(String[] args) {
        System.out.println(NUM);
        System.out.println(MyInterface.NUM);
    }
}

函数式接口(jdk8引入)

  • 只有一个抽象方法的接口(可以有很多默认方法)被称为函数式接口。函数式接口可以使用 lambda 表达式来简化代码。在 Java 中,@FunctionalInterface 可检测接口是否符合函数式接口。

java Lambda表达式:在 Java 8 中引入了 Lambda 表达式,主要用于简化对函数式接口的实现。ambda 表达式允许你以更简洁的方式写出匿名内部类,尤其是对于需要传递行为(如排序、遍历、事件处理等)的场景。Lambda表达式语法:

(parameters) -> expression
或者
(parameters) -> { statements; }

关于Lambda的介绍可参考,要理解Lambda这个表达式实际上是一个接口类型。后面就不难理解了。

使用 Lambda 表达式简化函数式接口的例子:使用 Comparator 排序。

传统方法:

List<String> names = Arrays.asList("John", "Jane", "Doe");
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

Lambda简化:

List<String> names = Arrays.asList("John", "Jane", "Doe");
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

java8中定义了很多函数式接口,供我们使用。用法可参考:https://blog.csdn.net/weixin_58473601/article/details/125231163。

接口与抽象类的区别

接口更抽象。接口中的所有方法默认都是 abstract 的,而抽象类可以包含具体方法,因此接口的抽象程度比抽象类更高。

内部类

局部内部类

  • 局部内部类是在方法或作用域内定义的类。它的作用范围仅限于定义它的代码块中,离开了这个范围,它就不可访问。

静态内部类

  • 静态内部类是用 static 修饰的内部类。它与外部类的实例无关,可以独立创建实例。静态内部类更像是外部类的一个静态成员,可以直接通过外部类访问。

匿名内部类(重点)

  • 匿名内部类是一种没有名字的内部类,常用于简化代码,尤其是在实现接口或继承类时。匿名内部类适合只需要使用一次的类。
  • 它通常用于实现接口的方法或者继承类时,并在实例化的同时重写其中的方法。
interface Animal {
    void makeSound();
}

public class Main {
    public static void main(String[] args) {
        Animal cat = new Animal() {
            @Override
            public void makeSound() {
                System.out.println("Meow");
            }
        };
        // 当然,也可以用lambda简化:Animal cat = () -> System.out.println("miao");
        
        cat.makeSound();  // 输出:Meow
    }
}

Java在类中的各种权限:public protected,private, default

访问修饰符类内部同一包内不同包中的子类不同包中的类
public
protected
default
private

注意

  1. 类的修饰符:Java 的类可以使用 publicdefault(无修饰符)。类不能使用 protectedprivate 修饰符。
  2. 接口的修饰符:接口可以是 publicdefault,而接口的所有字段默认是 public static final,所有方法默认是 public abstract(Java 8 及以上允许接口中有 defaultstatic 方法)。

异常

Throwable类(Error和Exception)

参考

Java的Throwable类是Java语言中所有错误和异常的超类。它位于java.lang包下,是Java异常处理机制的核心。Throwable类及其子类实例代表了程序执行中可能出现的异常情况和错误。在Java中,Throwable有两个主要的子类:ErrorException

  1. Error: 表示一些严重的错误,通常是不应该被应用程序尝试捕获的问题,如OutOfMemoryError,StackOverflowError等。
  2. Exception: 表示需要被应用程序捕获的异常条件,它又分为两类:
    检查型异常(Checked Exceptions): 这种异常在编译时强制要求处理,比如IOException、SQLException等。(比较严重)
    非检查型异常(Unchecked Exceptions): 这类异常包括运行时异常(RuntimeException)如NullPointerException、IndexOutOfBoundsException等,以及错误(Error)。

也就是说 检查型异常比较严重,需要处理!非检查型异常不严重,可以不处理。

五个捕获错误关键字

  • try 块:包含可能会抛出异常的代码。
  • catch 块:一旦异常发生,控制权跳到 catch 块,执行其中的异常处理代码。
  • finally 块:无论是否发生异常,finally 块中的代码始终会执行。
  • throw:手动抛出一个异常。
  • throws:在方法签名中声明该方法可能抛出的异常。
public class Hello{

    // 使用throws字段,方法抛出ArithmeticException错误
    public static int divide(int a, int b) throws ArithmeticException {
        if(b == 0) {
            throw new ArithmeticException("Cannot divide by zero!");
        }
        return a / b;
    }

    public static void main(String[] args) {
        try {
            // 尝试进行除法操作
            int result = divide(10, 0);
            System.out.println("Result:" +result);
        } catch (ArithmeticException e) {
            System.out.println("Exception caught:" + e.getMessage());
        } finally {
            System.out.println("This is the finally block. Always executed!");
        }
    }
}

try-with-resources

try-with-resources 是 Java 7 引入的一种语法,用于简化资源管理,特别是处理那些实现了 AutoCloseable 接口的资源,如输入/输出流、数据库连接等。它的主要优点是可以确保在使用完资源后自动关闭,减少了资源泄露的风险。

  1. 自动关闭资源:在 try 代码块结束时,所有在括号内声明的资源都会自动调用其 close() 方法,从而释放资源。
  2. 简化代码:通过自动管理资源的关闭,减少了手动编写 finally 代码块的需要。
  3. 异常处理:如果在 try 块中发生异常,资源依然会被关闭。同时,如果在关闭资源时也发生异常,这些异常会被捕获并可供后续处理。

语法

try (ResourceType resource1 = new ResourceType();
     ResourceType resource2 = new ResourceType()) {
    // 使用资源进行操作
} catch (ExceptionType e) {
    // 处理异常
}
// 资源在此处自动关闭

自定义异常

自定义异常可以继承 Exception(受检异常)或 RuntimeException(非受检异常)

1.自定义异常类,继承自 Exception

class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class Hello{
    // 定义一个方法,抛出自定义异常
    public static void validateAge(int age) throws InvalidAgeException {	// 注意!!这里throws不能省略
        if (age < 18) {
            // 当年龄小于 18 时,抛出自定义异常
            throw new InvalidAgeException("Age must be greater than or equal to 18");
        } else {
            System.out.println("Valid age: " + age);
        }
    }

    public static void main(String[] args) {
        try {
            // 调用 validateAge 方法,传入一个无效的年龄
            validateAge(15);
        } catch (InvalidAgeException e) {
            // 捕获并处理自定义异常
            System.out.println("Exception caught: " + e.getMessage());
        }
    }
}

2.使用 RuntimeException 创建非受检异常

class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

非受检异常不需要在方法签名中声明 throws,也不要求在编译时处理。也就是说,上面的validateAge可以不用throws InvalidAgeException

一些常用类

Object类

在Java中,Object类是所有类的根类。

下面是Object类中定义的一些方法

== 和 equals方法

equals 是 Object 中的方法.

== 是一个运算符,可以判断基本类型也可以判断引用类型。判断基本类型时,就是判断两者的值是否相等;判断引用类型时,就是判断两者的地址是否相等,也就是说判断实际对象是不是同一个。

equals 是 Object 中的方法,只能判断引用类型,也是通过判断两者地址是否相等。一般情况下,子类会重写这个 equals 方法,以添加判断内容是否相等的功能。

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println(str1 == str2);       // true
        System.out.println(str1.equals(str2));  // true

        String str3 = new String("Hello");
        String str4 = new String("Hello");
        System.out.println(str3 == str4);       // false
        System.out.println(str3.equals(str4));  // true

== 操作符用于比较两个引用是否指向同一个对象,而不是比较它们的值是否相同。 所以在第一个判断中,str1str2 都是直接用字面量 "Hello" 初始化的。Java 编译器在加载字面量字符串时会将它们放入**字符串常量池(string pool)**中。如果两个字符串字面量相同,它们会引用池中的同一个对象。

字符串常量池:在使用字面量方式创建字符串时(如 String str = "Hello";),Java 会在字符串常量池中检查是否已经存在相同内容的字符串。如果存在,它会复用池中的对象。

而第二个例子中使用 new 关键字创建字符串时,每次都会在堆中分配一个新的对象,即使两个对象的内容相同。所以str3 == str4false

hashCode 方法

hashCode() 的主要目的是通过返回一个整数来表示对象的散列值(哈希码),这个值用于确定对象在基于哈希的数据结构中的位置。通过 hashCode(),可以提高这些数据结构的性能,使其能够快速插入、查找和删除数据。

Person p1 = new Person();
Person p2 = new Person();
Person p3 = p2;
System.out.println(p1.hashCode());  // 356573597
System.out.println(p2.hashCode());  // 1735600054
System.out.println(p3.hashCode());  // 1735600054

在 Java 中,hashCode()Object 类中的一个方法,所有 Java 类都继承自 Object,因此每个 Java 对象都有一个 hashCode 方法。它的作用是返回对象的散列码(哈希码),它是用于支持基于哈希的数据结构(如 HashMapHashSetHashtable 等)的内部算法的。

hashCode() 的作用

hashCode() 的主要目的是通过返回一个整数来表示对象的散列值(哈希码),这个值用于确定对象在基于哈希的数据结构中的位置。通过 hashCode(),可以提高这些数据结构的性能,使其能够快速插入、查找和删除数据。

hashCode()equals() 的关系

在使用基于哈希的数据结构时,hashCode()equals() 方法通常是一起工作的。它们之间有一个重要的约定:

  1. 如果两个对象根据 equals() 方法是相等的,那么它们的 hashCode() 必须相同
  2. 如果两个对象的 hashCode() 不相同,那么它们肯定不是相等的
  3. 如果两个对象的 hashCode() 相同,它们未必是相等的,但会被认为是在同一个哈希桶中,需要进一步通过 equals() 来确定它们是否真正相等。

来看看String类型的hashCode运行结果。

        String str1 = "Hello";
        String str2 = "Hello";
        String str3 = new String("Hello");
        String str4 = new String("Hello");
        System.out.println(str1.hashCode()+" "+str2.hashCode());	// 69609650 69609650
        System.out.println(str3.hashCode()+" "+str4.hashCode());	// 69609650 69609650

在 Java 中,hashCode() 方法的默认实现会根据对象的内存地址来生成哈希值。然而,对于像 String 这样的类,hashCode() 方法已经被重写,基于字符串的内容(字符序列)来生成哈希值,而不是对象的内存地址。这是为什么你会看到这些字符串对象的哈希码是相等的,即使它们是不同的对象。

toString 方法

Object类中,toString的默认实现返回的是对象的全类名(包名加类名)和对象的哈希码。

System.out.println(new Person());	// com.zy.Person@1540e19d

当然,我们也可以重写toString方法。

class A{
    @Override
    public String toString() {
        return "hahaha";
    }
}

public class Hello{
    public static void main(String[] args) {
        System.out.println(new A()); // hahaha
    }
}
clone()方法

clone() 方法默认是浅拷贝,如果对象包含引用类型的数据,只有对象的引用会被复制,实际对象不会被复制。

展示深浅拷贝

getClass()方法

返回运行时对象的类类型。它返回一个 Class 对象,用于表示此对象的类。可以用来获取对象的类型,通常与反射一起使用。

class Person{
}

public class Hello{
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.getClass());  // class com.zy.Person
    }
}
notify()方法

唤醒等待在此对象上的一个线程,必须在同步块中使用。

wait()方法

使当前线程等待,释放锁,直到被唤醒或超时,也必须在同步块中使用。

Math类

Math类很多,大概看一下吧。需要用到再查即可。

abs():返回一个数的绝对值
max():返回两个数中较大的那个数
min():返回两个数中较小的那个数
pow():返回一个数的某个次幂
sqrt():返回一个数的平方根
sin():返回一个数的正弦值
cos():返回一个数的余弦值
tan():返回一个数的正切值
log():返回一个数的自然对数
exp():返回一个数的指数值
ceil():返回一个数的上限整数
floor():返回一个数的下限整数
round():返回一个数的四舍五入整数

Random类

在 Java 中,Random 类是用于生成伪随机数的类。它位于 java.util 包中,提供了一系列的方法来生成不同类型的随机数。Random 类不是线程安全的,因此在多线程环境中可能需要使用 ThreadLocalRandom 或者 SecureRandom 类。

构造方法

Random random = new Random();      // 使用当前时间作为种子
Random random2 = new Random(42L);  // 使用指定的种子

常用方法

        Random random = new Random();
        System.out.println(random.nextInt());       // 随机生成一个整数,这个整数的范围就是int类型的范围-2^31~2^31-1
		System.out.println(random.nextInt(100));	// 生成[0,100)之间的一个整数
        System.out.println(random.nextLong());      // 随机生成long类型范围的整数
        System.out.println(random.nextFloat());     // 随机生成[0, 1.0)区间的小数
        System.out.println(random.nextDouble());    // 随机生成[0, 1.0)区间的小数
        System.out.println(random.nextBoolean());   // 随机生成一个boolean值,生成true和false的值几率相等,也就是都是50%的几率
        System.out.println(random.nextGaussian());  // //随机生成呈高斯(“正态”)分布的 double 值,其平均值是 0.0,标准差是 1.0
        byte[] byteArr = new byte[5];
        random.nextBytes(byteArr);  // 随机生成byte,并存放在定义的数组中,生成的个数等于定义的数组的个数
        System.out.println(Arrays.toString(byteArr));

File类

实现创建,写入,读取,删除文件。

try {
            // 1.创建文件
            File file = new File("example.txt");

            if(file.createNewFile()) {
                System.out.println();
            } else {
                System.out.println("File already exitsts");
            }

            // 2.写入文件
            FileWriter writer = new FileWriter(file);
            writer.write("This is a sample text");
            writer.close();
            System.out.println("Successfully wrote to the file.");


            // 3.读取文件
            FileReader reader = new FileReader(file);
            // 用BufferedReader更高效的读取数据。并且它还提供了readLine()方法。
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line;
            System.out.println("Reading the file:");
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            bufferedReader.close();

            // 4.删除文件
            if (file.delete()) {
                System.out.println("File deleted: " + file.getName());
            } else {
                System.out.println("Failed to delete the file.");
            }

        }catch (IOException e) {
            System.out.println("An error occurred.");
            e.printStackTrace();
        }

包装类

基本数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean
装箱(Boxing)

装箱是将基本数据类型转换为其对应的包装类对象的过程。装箱分为手动装箱和自动装箱(从 Java 5 开始支持)。

手动装箱

int a = 10;
Integer boxedA = new Integer(a);  // 手动装箱

自动装箱

int b = 20;
Integer boxedB = b;  // 自动装箱
拆箱(Unboxing)

手动拆箱

Integer c = new Integer(30);
int unboxedC = c.intValue();  // 手动拆箱

自动拆箱

Integer d = 40;
int unboxedD = d;  // 自动拆箱
装箱与拆箱的应用场景

在使用集合类(如 ArrayList, HashMap 等)时,集合类只能存储对象,不能存储基本数据类型。这时,基本数据类型需要包装成包装类对象。这就是自动装箱的一个常见场景。

ArrayList<Integer> list = new ArrayList<>();
list.add(100);  // 自动装箱,将 100 转换为 Integer 对象
int num = list.get(0);  // 自动拆箱,将 Integer 对象转换为 int
包装类的缓存机制

对于某些数值,包装类会缓存对象。例如,Integer 类会缓存 -128127 之间的数值对象,因此这些范围内的装箱值可以被复用,不会每次都创建新的对象。(好像想起来了,当时Java老师还给我们举了类似下面这个例子)

Integer a = 127;
Integer b = 127;
System.out.println(a == b);  // true,因为 127 在缓存范围内

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  // false,因为 128 不在缓存范围内

// 注意:== 和 equals() 的区别:对于包装类对象,== 比较的是对象的引用,而 equals() 比较的是对象的值。

Data类

Date 类是 Java 中表示日期和时间的类,它提供了一些基本的日期和时间操作方法。这个类的实例包含一个时间戳,表示自 Unix 纪元(1970年1月1日)以来的毫秒数。

常用方法

  • new Date():获取当前日期和时间。
  • getTime():返回自 1970 年 1 月 1 日 UTC 以来的毫秒数。
import java.util.Date;

public class DateExample {
    public static void main(String[] args) {
        Date now = new Date();  // 获取当前日期时间
        System.out.println("当前时间: " + now);

        long timeInMillis = now.getTime();  // 获取当前时间的时间戳(毫秒数)
        System.out.println("时间戳: " + timeInMillis);
    }
}

// 当前时间:Thu Sep 26 14:39:35 CST 2024
// 时间戳:1727332775366

SimpleDateFormat类

SimpleDateFormat 是一个用于格式化和解析日期的类。它可以将日期格式化为指定的字符串,或将字符串解析为日期对象。

常用方法:

  • format(Date):将日期格式化为字符串。
  • parse(String):将字符串解析为日期对象。
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatExample {
    public static void main(String[] args) {
        Date now = new Date();  // 获取当前时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 定义日期格式
        String formattedDate = sdf.format(now);  // 格式化日期
        System.out.println("格式化后的日期: " + formattedDate);

        // 解析字符串为日期
        try {
            Date parsedDate = sdf.parse("2023-09-21 12:00:00");
            System.out.println("解析后的日期: " + parsedDate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 格式化后的日期:2024-09-26 14:43:17
// 解析后的日期: Thu Sep 21 12:00:00 CST 2023

Calendar类

Calendar 类是一个更强大的日期类,它不仅可以获取日期和时间,还可以对日期进行操作(如加减年、月、日等)。虽然 Date 类是简单的日期表示,Calendar 提供了更多的操作方法和功能。Calendar 类是抽象类,通常使用 Calendar.getInstance() 来创建一个 GregorianCalendar 对象。

常用方法

  • getInstance():获取一个 Calendar 实例,表示当前时间。
  • get(int field):获取指定时间字段的值,如 Calendar.YEAR, Calendar.MONTH 等。
  • add(int field, int amount):对指定时间字段进行加减操作。
import java.util.Calendar;

public class CalendarExample {
    public static void main(String[] args) {
        Calendar calendar = Calendar.getInstance();  // 获取当前日期时间的 Calendar 实例

        int year = calendar.get(Calendar.YEAR);  // 获取年份
        int month = calendar.get(Calendar.MONTH) + 1;  // 获取月份,月份从 0 开始,因此要加 1
        int day = calendar.get(Calendar.DAY_OF_MONTH);  // 获取日期

        System.out.println("当前日期: " + year + "-" + month + "-" + day);

        // 对日期进行操作
        calendar.add(Calendar.YEAR, 1);  // 当前时间加 1 年
        int newYear = calendar.get(Calendar.YEAR);
        System.out.println("加一年后的年份: " + newYear);
    }
}

String 类

String 类用于表示不可变的字符串。每当对字符串进行修改时,实际上会创建一个新的 String 对象,而不是修改原有的字符串。

特点:

  • 不可变性String 对象一旦创建,它的内容就不能被改变。每次修改(如拼接、替换)时,都会生成新的 String 对象。
  • final 修饰符String 类被 final 修饰,无法被继承。
  • 效率低(操作多时):由于不可变性,每次修改字符串都会创建新的对象,所以当操作大量字符串时,效率较低。

适用场景:

  • 字符串的内容不需要频繁改变。
  • 操作量较少的字符串,如常量、配置信息等。

StringBuffer 类

StringBuffer 类用于表示可变的字符串。与 String 不同,StringBuffer 可以在不创建新对象的情况下修改其内容。

特点:
  • 可变性StringBuffer 对象的内容可以直接进行修改,如追加、插入、删除等操作。
  • 线程安全StringBuffer 是线程安全的,它的操作是同步的(内部使用 synchronized 关键字进行方法锁定),因此在多线程环境下,多个线程可以安全地修改同一个 StringBuffer 对象。
  • 效率相对低:由于线程安全机制的存在,StringBuffer 在多线程的环境下效率相对较高,但在单线程环境下由于同步操作,效率低于 StringBuilder
适用场景:
  • 需要在多线程环境下频繁修改字符串的场合。
  • 需要保证线程安全的字符串操作。
public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");
        sb.append(" World");  // 直接修改当前对象
        System.out.println(sb);  // 输出 "Hello World"
    }
}

StringBuilder 类

StringBuilder 类也是一个可变的字符串类,与 StringBuffer 类似,能够修改字符串内容。不同的是,StringBuilder 不保证线程安全。

特点:
  • 可变性:与 StringBuffer 一样,StringBuilder 对象的内容可以直接修改。
  • 非线程安全StringBuilder 没有同步机制,因此在多线程环境下是不安全的,但在单线程环境下效率更高。
  • 效率高:由于没有线程同步的开销,在单线程场景中,StringBuilder 的性能要比 StringBuffer 高。
适用场景:
  • 在单线程环境下需要频繁修改字符串时,StringBuilder 是最佳选择。
  • 大量字符串拼接或修改的场合。
public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");  // 直接修改当前对象
        System.out.println(sb);  // 输出 "Hello World"
    }
}

String三兄弟对比

特性StringStringBufferStringBuilder
可变性不可变可变可变
线程安全性线程安全(不可变)线程安全(使用同步机制)非线程安全
性能操作大量时性能低操作大量时性能一般(同步)操作大量时性能高(单线程)
适用场景操作少、不变字符串多线程环境中字符串操作单线程环境中字符串操作
// 视频中的一个小测试
// "a" + 1 + 2 = ?
// 'a' + 1 + 2 = ?
// 1 + 2 + "a" = ?


public class hello{
    public static void main(String[] args) {
        String str1 = "a" + 1 + 2;		// a12
//        String str2 = 'a' + 1 + 2;      // 报错,结果是int类型
        String str3 = 1 + 2 + "a";		// a3
        System.out.println(str1+" " +str3);
    }
}

集合框架(Collection Framework)

后续用Java刷题会接触的更多,所以现在就大概了解一下即可。

集合框架提供了一套用于操作对象集合的标准接口和类,主要包括 CollectionMap 两大核心接口。

  • Collection 专注于 元素的管理。(一个元素)
  • Map 专注于 键值对的映射和查找。(两个元素)

在这里插入图片描述

Collection 接口

List (有序且可重复)List 接口表示元素按插入顺序存储,允许重复元素。常见实现类有:

  • ArrayList:基于动态数组实现,适合频繁读取数据的场合。
  • LinkedList:基于链表实现,适合频繁插入和删除操作的场合。
  • (不常用)Vector:与 ArrayList 类似,也是基于动态数组实现的集合类,但它是线程安全的,效率比 ArrayList 略低。
  • Stack:继承自 Vector,实现了栈的数据结构,遵循后进先出(LIFO)的规则。

Set (无序且不可重复)Set 接口用于存储不重复元素的集合,不保证元素的插入顺序。常见实现类有:

  • HashSet:基于哈希表实现,插入和查找操作的时间复杂度为 O(1)。(实际上HashSet内部是HashMap实现的)
  • TreeSet:基于红黑树实现,元素有序,支持按自然顺序或比较器顺序排序。(内部由TreeMap实现)

Map 接口

Map 接口用于存储键值对。每个键对应唯一的值,不允许重复的键。常见实现类有:

  • HashMap(重点,面试常考):基于哈希表实现的 Map,允许 null 键和 null 值。常用于快速查找、插入和删除。
    • JDK 1.7 中,HashMap 的底层结构是数组和链表的结合。(数组+链表)
    • JDK 1.8 中,当链表长度超过一定阈值(默认为 8)时,HashMap 使用红黑树替代链表,以提高性能。(数组+链表+红黑树)
  • TreeMap:基于红黑树实现的 Map,保证键的有序性,适用于按自然顺序或自定义顺序排序的场景。

Iterator 接口

Iterator 是集合的迭代器接口,提供了遍历集合元素的标准方法。常用方法:

  • hasNext(): 检查是否有下一个元素。
  • next(): 获取下一个元素。
  • remove(): 从集合中移除当前迭代器返回的元素。

Collections 工具类

Collections 是 Java 提供的一个工具类,位于 java.util 包中,包含了对集合进行操作的静态方法。它提供了对集合(如 List, Set, Map 等)进行排序、搜索、线程安全化等功能。

比如:排序

  • sort(List<T> list):对 List 进行自然顺序排序,T 必须实现 Comparable 接口。
  • sort(List<T> list, Comparator<? super T> c):使用 ComparatorList 进行排序。

其他

  • reverse(List<?> list):反转 List 中的元素顺序。
  • shuffle(List<?> list):随机打乱 List 中元素的顺序。

这个就不细讲了,用到再查即可。

泛型 <>

泛型 (Generic) 是 Java 在 JDK 5.0 引入的一项功能,旨在增强代码的类型安全性和可读性。通过使用泛型,可以在编译时进行类型检查,避免了强制类型转换带来的问题。

泛型的基本用法

泛型的好处:

  1. 类型安全:在编译时检查类型,减少了运行时的 ClassCastException
  2. 代码可读性增强:泛型使得代码更清晰易懂,减少了不必要的类型转换。
  3. 代码重用:泛型允许编写更具通用性的代码,适用于多种不同类型。

泛型的基本语法:

class Box<T> {
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在上述例子中,T 是一个类型参数,可以在使用 Box 类时指定具体的类型。

Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);  // 自动装箱,避免了类型转换问题
int value = integerBox.getValue();  // 无需强制类型转换

可以看到,这里初始化的时候,右边是没有指定类型T的,因为编译器会自动推断出是Integer。这里实际上是用了 Java 7 引入的语法糖钻石运算符<>

Box<Integer> integerBox = new Box<>();	//java7前
Box<Integer> integerBox = new Box<Integer>();	// java7后

常见的泛型约束

? extends T

描述: ? extends T 表示该类型可以是 T 本身或者 T 的任何子类。

用途: 主要用于读取操作,即你只打算从数据结构中读取数据,不希望向其中写入。因为它可以保证读取出来的类型至少是 T 的类型或其子类。

List<? extends Number> list = new ArrayList<Integer>();
Number num = list.get(0);  // 可以安全地读取

在这个例子中,list 可以是 List<Integer>List<Double> 等,只要是 Number 的子类即可。我们可以安全地读取 Number 或其子类的对象。

但是不能向 list 中添加元素,因为编译器无法确定具体类型:

list.add(10);  // 编译错误

? super T

描述: ? super T 表示该类型可以是 T 本身或者 T 的任何父类。

用途: 主要用于写入操作,即你打算向数据结构中添加元素,因为它可以保证你添加的元素至少是类型 T 的对象。

List<? super Integer> list = new ArrayList<Number>();
list.add(10);  // 可以安全地写入

在这个例子中,list 可以是 List<Number>List<Object> 等,但不能确定其具体类型。

可以向 list 中添加 IntegerInteger子类对象,因为 list 至少接受 Integer。但是读取操作有一定的限制,只能读取为 Object,因为具体类型未知:

Object obj = list.get(0);  // 只能保证读取为 Object

总结

  • ? extends T: 上界通配符,用于读取数据,保证类型为 T 或其子类,但不适合写入。
  • ? super T: 下界通配符,用于写入数据,保证可以安全地添加类型为 T 或其子类的数据,但读取的数据只能被看作 Object
  • <?>: 无界通配符,用于接受任意类型,多用于通用方法或类,但只能以 Object 类型读取,不能写入。

泛型类与泛型方法

  • 泛型类

    :类定义时使用泛型,可以让类的属性、方法使用多种类型。

    class Pair<T, U> {
        private T first;
        private U second;
    
        public Pair(T first, U second) {
            this.first = first;
            this.second = second;
        }
    
        public T getFirst() { return first; }
        public U getSecond() { return second; }
    }
    
  • 泛型方法

    :方法定义时使用泛型,允许该方法可以处理多种类型。

    public <T> T getElement(List<T> list, int index) {
        return list.get(index);
    }
    // <T>:这是在方法名前声明的类型参数。表示该方法是一个泛型方法,并且 T 是一个类型参数。这个类型参数 T 可以是任何类型。
    // T getElement(List<T> list, int index) T表示返回值
    

避免类型转换问题

在没有泛型之前,集合操作需要频繁进行强制类型转换,容易引发类型转换错误。例如:

List list = new ArrayList();  // 未使用泛型
list.add("Hello");
list.add(123);

for (Object obj : list) {
    // 强制类型转换
    String str = (String) obj;  // 运行时会抛出ClassCastException
}

通过泛型,类型可以在编译时得到检查,从而避免类型转换问题:

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123);  // 编译时会报错

for (String str : list) {
    // 不需要强制类型转换
    System.out.println(str);
}

泛型消除了集合操作中手动进行类型转换的需要,确保了在编译时就能检测出类型错误,而不是等到运行时发生 ClassCastException

总结

  • Collections 工具类提供了大量处理集合的常用方法。
  • 泛型通过在编译时进行类型检查,避免了手动类型转换,增强了代码的安全性和可读性。
  • 泛型约束(如 ? extends T, ? super T)为代码的灵活性提供了更多可能。

IO流

根据流的方向可以把IO流分为 输入流(读取)和输出流(写出)。

根据操作文件类型可以把IO流分为 字节流(所有类型文件)和字符流(纯文本文件)。

根据流的角色的不同分为:节点流处理流

字节流

字节流用于处理原始字节数据,比如图片、音频等非文本数据。Java 提供了 InputStreamOutputStream 作为字节流的基础类。(即字节流进而分类为输入流和输出流)

字节输出流OutputStream

OutputStream 是一个抽象类,提供了向输出目标(如文件、网络连接等)写入字节的基本方法。常见的方法包括:

  • void write(int b):写入一个字节(0-255),b 是字节的整数表示。
  • void write(byte[] b):将字节数组中的所有字节写入输出流。
  • void write(byte[] b, int off, int len):从字节数组的指定位置写入指定长度的字节到输出流。
  • void flush():刷新输出流,确保所有缓存的数据都被写入目标。
  • void close():关闭输出流,释放相关资源。

OutputStream有很多子类,下面举个例子:使用FileOutputStream写入文件。

public class Hello {
    public static void main(String[] args) throws IOException {
        String data = "Hello, OutputStream!";
        FileOutputStream fos = new FileOutputStream("output.txt");
        fos.write(data.getBytes());
        //fos.flush();
        fos.close();
    }
}

如果想追加写,可以使用构造的第二个参数boolean append

FileOutputStream fos = new FileOutputStream("output.txt",true);

字节输入流InputStream

nputStream 是一个抽象类,提供了从输入源(如文件、网络连接等)读取字节的基本方法。常见的方法包括:

  • int read():读取下一个字节,并返回该字节的整数值(0-255),返回 -1 表示到达流的末尾。
  • int read(byte[] b):从输入流中读取字节并存储到字节数组中,返回实际读取的字节数。
  • int read(byte[] b, int off, int len):从输入流中读取最多 len 个字节,并将其存储到字节数组 b 的指定偏移量 off 中。
  • void close():关闭输入流,释放相关资源。

示例:使用 FileInputStream 读取文件

public class Hello {
    public static void main(String[] args) throws IOException {
        try(FileInputStream fis = new FileInputStream("output.txt");) {
            int content;
            // 注意fis.read()返回的是int类型
            while((content = fis.read()) != -1) {
                System.out.print((char)content);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

组合使用,复制一张图片

    public static void main(String[] args) {
        try(FileInputStream fis = new FileInputStream("source.jpg");
            FileOutputStream fos = new FileOutputStream("des.jpg")) {
            
            byte[] buffer = new byte[1024]; //缓冲区
            int bytesRead;
            while((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0 ,bytesRead);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

如果把上面代码缓冲区去掉,变成一个字节一个字节读写,会发现非常慢。

硬盘读写数据的最小单位是块,而不是字节。块的大小通常是 4KB 或更大。如果程序一次只处理一个字节,这与硬盘的最小块大小不匹配,可能会造成效率低下。通过缓冲区,程序一次可以读取多个字节,优化硬盘的读写操作。

大多数文件系统(如 NTFS、EXT4 等)的磁盘块大小通常是 4 KB,也就是 4096 字节。因此,1024 字节的缓冲区大小很容易与文件系统的块大小相结合。虽然 1 KB 小于 4 KB,但它仍然能提供足够的缓冲并减少频繁的读写调用。

字符流

字符流用于处理文本数据,比如文本文件。Java 提供了 ReaderWriter 作为字符流的基础类。

字符输入流Reader

Reader 是一个抽象类,用于读取字符流。常见的方法有:

  • int read():读取单个字符并返回对应的字符编码值(int 类型)
  • int read(char[] cbuf):尝试读取多个字符并将其存储到字符数组 cbuf
  • int read(char[] cbuf, int off, int len)
  • boolean ready():返回是否可以无阻塞地读取字符,通常用于判断流是否准备好进行读操作。
  • long skip(long n):跳过并丢弃流中最多 n 个字符,返回实际跳过的字符数。
  • void close()

String readLine()BufferedReader 独有):读取一行文本并返回一个字符串。如果到达流的末尾

常见的实现类有:

  • FileReader:用于从文件中读取字符。
  • BufferedReader:为其他 Reader 类提供缓冲能力,提高读取效率。

示例:使用FileReader读取文件。

    public static void main(String[] args) {
        try(FileReader reader = new FileReader("output.txt")) {
            int character;
            while((character = reader.read()) != -1) {
                System.out.print((char) character);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

使用BufferedReader提高读取效率

    public static void main(String[] args) {
        try(BufferedReader reader = new BufferedReader(new FileReader("output.txt"))) {
            String line;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
字符输出流Writer

Writer 是一个抽象类,用于写入字符流。

  • void write(int c)
  • void write(char[] cbuf)
  • void write(char[] cbuf, int off, int len)
  • void write(String str)
  • void write(String str, int off, int len)
  • void flush()
  • void close()

常见的实现类有:

  • FileWriter:用于将字符写入文件。
  • BufferedWriter:为其他 Writer 类提供缓冲能力,提高写入效率。

示例:用FileWriter写入

    public static void main(String[] args) {
        try (FileWriter writer = new FileWriter("output.txt")) {
            writer.write("Hello, World!\n");
            writer.write("This is a test.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

使用 BufferedWriter 提高写入效率:

    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            writer.write("Hello, World!");
            writer.newLine(); // 添加换行符
            writer.write("This is a test.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

节点流

节点流是直接与数据源或数据目的地交互的流。它们可以直接从文件、内存、管道等中读取或向这些位置写入数据。节点流是最基础的流,主要用于物理设备的输入/输出操作。

常见的节点流:

  • FileInputStreamFileOutputStream:处理文件的字节输入和输出。
  • FileReaderFileWriter:处理文件的字符输入和输出。
  • CharArrayReaderCharArrayWriter:将字符数据存储到字符数组中。
  • ByteArrayInputStreamByteArrayOutputStream:处理内存中的字节数据。
  • PipedInputStreamPipedOutputStream:实现线程间的管道通信。

处理流

处理流是一种包装在其他流上,提供额外功能的流。处理流不能直接与数据源或数据目的地交互,而是依赖于节点流来完成实际的输入输出操作。它们主要用于在节点流的基础上进行数据处理,比如缓冲、过滤、数据转换等。

常见的处理流:

  • BufferedInputStreamBufferedOutputStream:通过缓冲区来提高字节输入/输出的效率。
  • BufferedReaderBufferedWriter:通过缓冲区来提高字符输入/输出的效率。
  • DataInputStreamDataOutputStream:允许从流中读写Java的基本数据类型(如int, long, double)。
  • ObjectInputStreamObjectOutputStream:用于序列化和反序列化Java对象。
  • InputStreamReaderOutputStreamWriter:用于将字节流转换为字符流,或将字符流转换为字节流。
  • PrintWriterPrintStream:提供便捷的写入方法,比如 print()println(),主要用于文本数据输出。
DataInputStream 和 DataOutputStream

示例

public static void main(String[] args) {
        // 写入数据
        try(DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"))) {
            dos.writeInt(1234);
            dos.writeDouble(12.34);
            dos.writeBoolean(true);
        }catch (IOException e) {
            e.printStackTrace();
        }

        // 读取数据
        try(DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {
            int i = dis.readInt();
            double d = dis.readDouble();
            boolean b = dis.readBoolean();
            System.out.println("Read values: " + i + ", " + d + ", " + b);
        }catch (IOException e) {
            e.printStackTrace();
        }

    }

DataInputStream/DataOutputStream 提供了对基本数据类型进行读写的方便接口,避免了手动处理字节流。常用于需要将基本数据类型以二进制形式存储或传输的场景,比如保存配置文件、网络通信等。

ObjectInputStream 和 ObjectOutputStream(序列化和反序列化)

ObjectInputStream:用于从输入流中反序列化对象,即将字节流转化为 Java 对象。

ObjectOutputStream:用于将 Java 对象序列化并写入输出流,即将对象转化为字节流。

示例

class Person implements Serializable {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class Hello {
    public static void main(String[] args) throws IOException, ClassNotFoundException  {
        // 序列化对象到文件
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
            Person person = new Person("Alice", 30);
            oos.writeObject(person);
        }

        // 从文件中反序列化对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
            Person person = (Person) ois.readObject();
            System.out.println("Deserialized person: " + person);
        }
    }
}
PrintWriter 和 PrintStream
  • PrintWriter:是字符流,用于格式化输出文本数据。它提供了很多便捷的 printprintln 方法,常用于写入文件或显示在控制台。
  • PrintStream:是字节流,类似于 PrintWriter,但它处理字节数据,常用于打印字节流到文件或控制台。
// 使用 PrintWriter 将字符写入文件
        try (PrintWriter pw = new PrintWriter(new FileWriter("printwriter.txt"))) {
            pw.println("Hello, PrintWriter!");
            pw.printf("Formatted string: %d, %.2f\n", 10, 3.14);
        }

        // 使用 PrintStream 将字节写入文件
        try (PrintStream ps = new PrintStream(new FileOutputStream("printstream.txt"))) {
            ps.println("Hello, PrintStream!");
            ps.printf("Formatted string: %d, %.2f\n", 10, 3.14);
        }

PrintWriter 适合处理字符数据,而 PrintStream 处理字节数据。System.out 本质上是一个 PrintStream,可以直接用于输出到控制台。

序列化和反序列化

  • 序列化:ObjectOutputStream,通过 writeObject() 方法进行对象的序列化。
  • 反序列化:ObjectInputStream,通过 readObject() 方法进行对象的反序列化。
Serializable 接口

Serializable 是一个标记接口(没有任何方法),即空接口。当一个类实现 Serializable 接口时,表示该类的对象可以被序列化。

问题:作为一个空接口,那我不implements它,别人是怎么知道并报出对应错误的?

答:在执行序列化时,JVM会使用反射机制来检查一个类是否实现了 Serializable 接口。如果类没有实现 Serializable 接口,序列化过程会直接失败,抛出 NotSerializableException。这就是JVM能够知道一个类是否实现了 Serializable 的原因。

transient 关键字
  • transient 是Java的关键字,表示某个字段不参与序列化。即当对象被序列化时,transient 修饰的字段不会被序列化到字节流中。
  • 当对象反序列化时,transient 字段会被设置为默认值(例如 null0false 等)。

用法

class User implements Serializable {
    String username;
    transient String password; // 不序列化此字段
}

多线程

JUC是什么:即java.util .concurrent,在JDK1.5引入。是一个处理线程的工具包。

视频后面还提到了多线程,对于刚做完C++中webServer项目的我,感觉很熟悉hhh。 像视频中提到的什么线程池这些概念,都是认识的,只不过Java写的方式不太一样罢了。

重要的是基础,对于代码层面的,以后需要接触再速成一下即可。

视频中间还有网络编程和GUI,都不是重点内容,就先跳过了

注解和反射

注解(Annotation)

JDK5 时引入的新特性。注解是Java提供的一种元数据机制,用于为代码中的类、方法、字段等元素提供附加信息。在Java中,注解不会直接影响代码的运行逻辑,而是为编译器或其他工具提供额外信息。

元注解

元注解是用于修饰其他注解的注解,例如@Retention, @Target等,用来定义注解的作用范围和生命周期。

  • Target注解的作用是:描述注解的使用范围
  • Reteniton注解的作用是:描述注解保留的时间范围
内置注解

Java中预定义的一些注解,如@Override(表示方法重写)、@Deprecated(标记过时的方法)等。

自定义注解

用户可以定义自己的注解,指定注解的目标和生命周期。自定义注解通过@interface关键字定义。

@Retention(RetentionPolicy.RUNTIME)	 	//元注解注解我们的自定义注解
public @interface MyAnnotation {
}

// 使用注解
@MyAnnotation
class Person{
}

反射(Reflection)

反射是Java的一种动态机制,允许在运行时获取类的结构信息(如方法、构造函数、字段等),并能动态调用方法、构造实例等。反射是Java提供的非常强大的功能,但滥用反射可能导致性能问题,并破坏代码的安全性。

推荐视频:Java中的反射 Reflection in Java

获取Class对象

获取Class对象有三种主要方式

1.Class<User> userClass = User.class; 在编译时就确定了具体的类,属于静态引用。使用这种方式获得类对象时,不会立即触发类的静态初始化块。

2.User user = new User(); Class<?> clazz = user.getClass();
这个class实在运行时从User实例获取的,而User的具体类型,只能在运行时创建和确定。在编译阶段无法准确判断Class对象的确切类型。因此使用通配符<?>。

3.Class<?> clazz = Class.forName("com.zy.user");通常用在类名在编译时不可知的场景中。使用这种方式获得类对象时,会触发静态初始化块。

Class对象方法

Class对象有很多方法,这里着重讲几个核心的。

1.Field[] fields = clazz.getDeclaredFields(); 这样可以获得类中所有字段(无论是否私有)但是不包括父类的字段;

2.Field[] fields = clazz.getFields(); 这样可以获得所有public的字段,包括父类的。

后面很多Declared字段和不包括Declared字段的都是一样的区别。

3.Field fields = clazz.getField("name"); 可以获得指定变量。

4.getDeclaredAnnotation(MyAnnotation.class)获得注解。

5.获得类中的值

        Class<?> clazz = Class.forName("com.zy.User");
        Field field = clazz.getDeclaredField("publicStaticField");
		field.setAccessible(true);	// 为了访问私有字段
        System.out.println(field.get(null));	//这里调用的是静态方法,不依附于任何实例,所以用null

6.获得所有方法

Class<?> clazz = Class.forName("com.zy.User");
        Method[] methods = clazz.getDeclaredMethods();
        for(Method method : methods) {
            System.out.println(method.getName());
        }

7.执行方法 invoke

        Class<?> clazz = Class.forName("com.zy.User");
        Method method = clazz.getDeclaredMethod("publicStaticMethod");
        method.invoke(null);    // 同样这里是静态方法

8.执行带参数的方法

        Class<?> clazz = Class.forName("com.zy.User");
        Method method = clazz.getDeclaredMethod(
                "publicStaticMethod",
                String.class);	//带String 参数
        method.invoke(null,"Hi here"); 	//调用时需要传入String 参数

9.获取构造器创建对象

Class<?> clazz = Class.forName("com.zy.User");
        //获取无参构造器
//        Constructor<?> constructor = clazz.getDeclaredConstructor();
        //获取有参构造器
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
        Object obj = constructor.newInstance("AAA",12);
        if(obj instanceof User) {
            User user = (User)obj;
        }

推荐的java反射视频中,最后有举个例子,利用反射做个小框架,建议看看。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/888778.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android Preference的使用以及解析

简单使用 values.arrays.xml <?xml version"1.0" encoding"utf-8"?> <resources><string-array name"list_entries"><item>Option 1</item><item>Option 2</item><item>Option 3</item&…

衡石分析平台系统管理手册-智能运维之系统设置

系统设置​ HENGSHI 系统设置中展示了系统运行时的一些参数&#xff0c;包括主程序相关信息&#xff0c;Base URL、HTTP 代理、图表数据缓存周期、数据集缓存大小、租户引擎等相关信息。 主程序​ 系统设置中展示了主程序相关信息&#xff0c;这些信息是系统自动生成的&#…

Linux 之 Linux应用编程概念、文件IO、标准IO

Linux应用编程概念、文件IO、标准IO 学习任务&#xff1a; 1、 学习Linux 应用开发概念&#xff0c;什么是系统调用&#xff0c;什么是库函数 2、 学习文件IO&#xff1a;包括 read、write、open、close、lseek 3、 深入文件IO&#xff1a;错误处理、exit 等 4、 学习标准IO&a…

TCP四次挥手过程详解

TCP四次挥手全过程 有几点需要澄清&#xff1a; 1.首先&#xff0c;tcp四次挥手只有主动和被动方之分&#xff0c;没有客户端和服务端的概念 2.其次&#xff0c;发送报文段是tcp协议栈的行为&#xff0c;用户态调用close会陷入到内核态 3.再者&#xff0c;图中的情况前提是双…

什么是PLM系统?PLM系统对制造业起到哪些作用?三品PLM系统对汽车制造业意义

在当今竞争激烈的制造业环境中&#xff0c;企业面临着来自市场、技术、客户需求等多方面的挑战。为了应对这些挑战&#xff0c;许多制造企业纷纷引入产品生命周期管理PLM系统&#xff0c;以实现更高效、更灵活的产品全生命周期管理。PLM系统以其独特的优势&#xff0c;在优化产…

【RAG论文精读3】RAG论文综述1(2312.10997)-第1部分

更多AI知识点总结见我的专栏&#xff1a;【AI知识点】 AI论文精读、项目和一些个人思考见我另一专栏&#xff1a;【AI修炼之路】 有什么问题、批评和建议都非常欢迎交流&#xff0c;三人行必有我师焉&#x1f601; 简介 论文中英文名 Retrieval-Augmented Generation for Lar…

解压缩软件哪个好?不同场景下的最佳选择

解压缩软件在日常工作与生活中发挥着至关重要的作用&#xff0c;从简单的文件解压到处理大型项目&#xff0c;选择一款适合自己的解压缩软件能够大幅提高工作效率。 面对众多解压缩工具&#xff0c;如WinRAR、7-Zip、解压专家、PeaZip等&#xff0c;如何根据不同的使用场景选择…

xss-labs靶场第一关测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、注入点寻找 2、使用hackbar进行payload测试 3、绕过结果 四、源代码分析 五、结论 一、测试环境 1、系统环境 渗透机&#xff1a;本机(127.0.0.1) 靶 机&#xff1a;本机(127.0.0.…

咸鱼sign逆向分析与爬虫实现

目标&#xff1a;&#x1f41f;的搜索商品接口 这个站异步有点多&#xff0c;好在代码没什么混淆。加密的sign值我们可以通过搜索找到位置 sign值通过k赋值&#xff0c;k则是字符串拼接后传入i函数加密 除了开头的aff…&#xff0c;后面的都是明文没什么好说的&#xff0c;我…

Apache DolphinScheduler社区9月进展记录

各位热爱 Apache DolphinScheduler 的小伙伴们&#xff0c;社区 9 月月报更新啦&#xff01;这里将记录 Apache DolphinScheduler 社区每月的重要更新&#xff0c;欢迎关注&#xff01; 月度 Merge Star 感谢以下小伙伴上个月为 Apache DolphinScheduler 做的精彩贡献&#x…

postman变量,断言,参数化

环境变量 1.创建环境变量 正式环境是错误的&#xff0c;方便验证环境变化 2.在请求中添加变量 3.运行前选择环境变量 全局变量 能够在任何接口访问的变量 console中打印日志 console.log(responseBody);//将数据解析为json格式 var data JSON.parse(responseBody); conso…

qt登录界面的完善

头文件1 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QPushButton> #include<QLineEdit> #include<QLabel>class Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget();int btn;int btn1; signal…

ssrf学习(ctfhub靶场)

ssrf练习 目录 ssrf漏洞 漏洞形成原理&#xff08;来自网络&#xff09; 寻找ssrf漏洞&#xff0c; 靶场题目 第一题&#xff08;url探测网站下文件&#xff09; 第二关&#xff08;使用伪协议&#xff09; 关于http和file协议的理解 file协议 http协议 第三关&…

Qt-窗口布局按钮输入类

1. 窗口布局 Qt 提供了很多摆放控件的辅助工具&#xff08;又称布局管理器或者布局控件&#xff09;&#xff0c;它们可以完成两件事&#xff1a; 自动调整控件的位置&#xff0c;包括控件之间的间距、对齐等&#xff1b; 当用户调整窗口大小时&#xff0c;位于布局管理器内的…

立即升级!Windows11 24H2 正式版 V26100.2033!

今日&#xff0c;系统之家小编给您带来2024年10最新推出了Windows11 24H2正式版系统下载&#xff0c;该版本系统以微软官方Windows11 24H2 26100.2033 专业版为基础&#xff0c;展开离线制作与优化&#xff0c;安全无毒&#xff0c;且修复了之前版本存在的蓝屏、绿屏等问题&…

手机怎样改网络ip地址?内容详尽实用

随着网络技术的发展&#xff0c;更改手机IP地址已成为一种常见需求。本文将详细介绍如何在不同网络环境下更改手机IP地址&#xff0c;包括移动网络和WiFi网络&#xff0c;以及同时适用于两种网络的方法&#xff0c;内容详尽实用&#xff0c;干货满满。 一、适用于移动网络&…

计算机毕业设计 基于Python的智能停车管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

SpringBoot项目 | 瑞吉外卖 | 短信发送验证码功能改为免费的邮箱发送验证码功能 | 代码实现

0.前情提要 之前的po已经说了单独的邮箱验证码发送功能怎么实现&#xff1a; https://blog.csdn.net/qq_61551948/article/details/142641495 这篇说下如何把该功能整合到瑞吉项目里面&#xff0c;也就是把原先项目里的短信发送验证码的功能改掉&#xff0c;改为邮箱发送验证…

前端vue-配置请求拦截器

1.配置拦截器&#xff0c;记得20行的导出 2.响应拦截器&#xff0c;记得28行的导出 3.拦截器不止可以拦截&#xff0c;还可以添加内容

XILINX MIG驱动

简介 框架图 本章节主要针对MIG读写做详细介绍,首先创建BLOCK DESIGN,工程连接如下图所示: MIG IP介绍 DATAMOVER的配置这里不再做介绍,结合上篇文章讲到DATAMOVER对BRAM进行读写操作,这里通过AXI桥再加一个MIG模块,MIG模块的配置和说明如下: 1、Clock Period:…