站在C/C++的肩膀速通Java面向对象(上)

默认学过C或C++,对变量、表达式、选择、循环都会。

Java 特点

运行特征

  1. 解释型语言(JavaScript、Python等)
    源文件-(平台专属解释器)->解释器中执行
  2. 编译型语言(C++、Go等)
    源文件-(平台编译器)->平台可执行文件
  3. Java
    源文件-(通用编译器)->.class二进制文件-(平台JVM)->JVM中执行

Java名词解释

  1. JVM(Java Virtual Machine)虚拟机
  2. JRE(Java Runtime Environment)运行环境(包含JVM和解释器),有这个就能运行Java程序
  3. JDK(Java Development Kit)包含JRE和相关工具的Java开发环境

常用Dos命令

dir查目录,不是ls
cd进目录
d:进D盘
mkdir创建文件夹
rd删除文件夹
cls清屏
exit退出

Java的相关文件(Windows平台)

  1. .java文件:Java源代码文件,包含了程序员编写的Java代码。
  2. .class文件:Java字节码文件,是由Java编译器编译生成的中间代码文件,可以由Java虚拟机(JVM)执行。
  3. .jar文件:JavaArchive文件,是一种归档文件格式,用于将多个Java类文件、资源文件和元数据打包成一个文件。通常用于分发和部署Java应用程序、库或组件。
  4. java.exe:Java程序的执行器,用于执行已编译成的Java字节码文件(.class文件)。
  5. javac.exe:Java编译器,用于编译Java源代码文件(.java文件)成为Java字节码文件(.class文件)。
  6. jar.exe:Java归档工具,用于创建和管理Java归档文件(.jar文件),可以将多个.class文件打包成一个可执行的JAR文件。
  7. javadoc.exe:Java文档生成工具,用于根据源代码中的注释生成API文档,将Java代码注释转换为HTML格式的文档。
  8. jdb.exe:Java调试器,用于在调试Java程序时启动Java调试器,允许暂停程序、检查变量值、设置断点等操作。
  9. javap.exe:Java反汇编器,用于反编译Java字节码文件(.class文件)成为Java源代码的形式,可以查看.class文件的内容,了解其中包含的类、方法、字段等信息。

一个Java程序开发通常如下:编写.java源文件,使用javac.exe编译成.class文件,使用java.exe执行.class文件(不能带.class后缀,直接java 类名)。

Java 基础语法

基本数据类型

  1. 整型(Integer Types):
    • byte:8位,有符号,范围为 -128 到 127。
    • short:16位,有符号,范围为 -32,768 到 32,767。
    • int:32位,有符号,范围为 -2^31 到 2^31 - 1。
    • long:64位,有符号,范围为 -2^63 到 2^63 - 1。123L
  2. 浮点型(Floating-Point Types):
    • float:32位,单精度浮点数,范围为大约 1.4e-45 到 3.4e+38,精确度为约 7 位小数。12.2f
    • double:64位,双精度浮点数,范围为大约 4.9e-324 到 1.8e+308,精确度为约 15 位小数。
  3. 字符型(Character Type):
    • char:16位,Unicode字符,范围为 ‘\u0000’(即 0)到 ‘\uffff’(即 65,535)。
  4. 布尔型(Boolean Type):
    • boolean:表示 true 或 false 值。

易错:image.png
image.png

引用数据类型

  1. 类(Class):类是用户定义的数据类型,用于创建对象。类包含属性(字段)和方法,可以通过实例化(创建对象)来访问它们。
  2. 接口(Interface):接口是一种抽象数据类型,用于定义类应该实现的方法。类可以实现一个或多个接口,以达到实现多态性和代码复用的目的。
  3. 数组(Array):数组是一组相同类型的数据元素的集合。数组在内存中是连续存储的,可以通过索引访问其中的元素。
  4. 枚举(Enum):枚举是一种特殊的类,用于定义一组命名的常量。枚举常量通常用于表示有限的一组可能值,如星期几、月份等。
  5. 字符串(String):字符串是Java中的特殊对象,用于表示文本数据。字符串是不可变的,可以进行各种操作,如连接、截取、替换等。
  6. 包装类(Wrapper Classes):包装类是一种将基本数据类型封装为对象的类。Java提供了一组包装类,如Integer、Double、Boolean等,用于在需要对象的上下文中使用基本数据类型。
  7. 自定义引用数据类型:除了以上提到的内置引用数据类型外,开发人员还可以创建自定义的引用数据类型,通过定义类和接口来实现自己的数据结构和算法。

控制台输入

import java.util.Scanner;

class UseScanner {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("Input your name: ");
        String name = input.next();	// 取字符串
        System.out.println("OK~ next... input your age: ");
        int age = input.nextInt();	// 取整数
        System.out.println("Hello " + name + ". And your age is " + age + ". Choose A or B please.");
        char a = input.next().charAt(0);	// 取字符串第0个字
        System.out.println("You choose " + a);
        input.close();
    }
}

switch语句中允许接受byte、short、int、char、String作为变量,但不支持long

数组

数组介绍

class UseArray {
    public static void main(String[] args) {
        int[] arr; // 声明数组
        arr = new int[5]; // 分配空间

        int[] arr2 = { 1, 2, 3, 4, 5 }; // 声明,并(包含分配空间)赋值

        printArray(arr);
        System.out.println();
        printArray(arr2);
    }

    static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}
class UseArray {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3 };

        // 拷贝扩容
        arr = Arrays.copyOf(arr, 10);
        printArray(arr);

        // 也能截取
        arr = Arrays.copyOf(arr, 2);
        printArray(arr);
    }

    static void printArray(int[] arr) {
        System.out.println(Arrays.toString(arr));
    }
}
import java.util.Arrays;

class UseArray {
    public static void main(String[] args) {
        int[] arr = { 1, 2, 3 };
        printArray(arr);

        arr = extendArray(arr);
        printArray(arr);

        addArray(arr);
        printArray(arr);
    }

    static void printArray(int[] arr) {
        System.out.println(Arrays.toString(arr));
    }

    // 传入的是arr所指的地址
    static void addArray(int[] arr) {
        arr[0] = 10086;
    }

    static void extendArray(int[] arr) {
        arr = Arrays.copyOf(arr, 10);
        return arr;
    }

}

image.png

可变长参数

只能放最后一个参数位置,作为数组形式使用。

class UseArray {
    public static void main(String[] args) {
        System.out.println(sumup("Hello", 1, 2, 3, 4, 5, 6, 7));
    }

    public static int sumup(String str, int... nums) {
        int sum = 0;
        System.out.println("Input String: " + str);
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }

}

二维数组

class UseArray {
    public static void main(String[] args) {
        int[][] arrs1 = new int[3][5];
        arrs1[0][0] = 0;
        printArrays(arrs1);

        int[][] arrs2 = new int[3][];
        arrs2[0] = new int[5];
        arrs2[1] = new int[3];
        arrs2[2] = new int[1];
        printArrays(arrs2);

        int[][] arrs3 = { { 1, 2, 3 }, { 2, 3 }, { 3 } };
        printArrays(arrs3);

        return;
    }

    public static void printArrays(int[][] arrs) {
        for (int i = 0; i < arrs.length; i++) {
            for (int j = 0; j < arrs[i].length; j++)
                System.out.print(arrs[i][j] + " ");
            System.out.println();
        }
        System.err.println("--------------");
        return;
    }

}

面向对象

面向对象基础

public class Person {
    String name;
    int age;

    public void speak(String words) {
        System.out.println(name + "say: " + words);
    }

    public void jump() {
        System.out.println(name + "jumps to " + age);
    }
}

public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小明";
        person.age = 18;
        person.speak("哈哈哈");
        person.jump();
    }
}
  1. 类由成员变量和方法组成
  2. 类中定义的变量是成员变量,实例化后存在堆空间中,创建后就有默认值,不会因为没有初始化成员变量而报错。
  3. 局部变量的优先级高于成员变量,在类方法中的同名局部变量会先于成员变量。如果在方法中需要使用成员变量,需要加上this.来访问。

方法重载

一个类中定义多个相同名称的方法,但是参数列表不同(类型、顺序、个数不同都算不同)。这与形参名、方法访问修饰符、返回值类型无关!
可以参考println()函数的实现,有很多方法重载的例子。、

 public void println(boolean x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}
public void println(char x) {
    if (getClass() == PrintStream.class) {
        writeln(String.valueOf(x));
    } else {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
}

构造方法

类中的特殊方法,与类名同名。
没有返回值类型,只在创建对象时调用,无法通过方法调用。
没有在类中显式定义构造方法,由编译器提供无参构造方法。
构造方法也可以重载!在定义带参构造后需要补充无参构造!

public class Person {
    String name;
    int age;

    public Person() {
        System.out.println("Person created!");
    }

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

    public void speak(String words) {
        System.out.println(name + "say: " + words);
    }

    public void jump() {
        System.out.println(name + "jumps to " + age);
    }
}

对象的创建步骤

image.png

this关键字

指向当前实例对象。实际是对象在虚拟机中的堆地址(在new的时候就已经确定)

  1. 在类方法中,可以避免与局部同名变量冲突。用于调用成员变量/方法。
  2. 构造方法内自调用this()
public class Person {
    String name;
    int age;

    public Person() {
        System.out.println("Person created!");
    }

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

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

    public void speak(String words) {
        System.out.println(name + "say: " + words);
    }

    public void jump() {
        System.out.println(name + "jumps to " + age);
    }
}

封装

隐藏必要的对象内部实现细节,控制对象属性、方法的使用与修改权限。
使用访问修饰符private(私有,仅本类可见)。
属性的访问与修改使用getter和setter。

public class Person {
    String name;
    int age;
    private int score;

    public void setScore(int score) {
        if (score >= 0 && score <= 100)
            this.score = score;
        else
            this.score = 0;
    }

    public int getScore() {
        return this.score;
    }
}

继承

使用extends关键字继承父类,实例化子类时,会先调用父类的构造方法,然后调用子类的构造方法。
默认会调用无参父类构造,如果需要调用带参构造,使用super(a,b,c...)调用。
子类创建对象时并没有创建父类对象,只是在子类空间中创建了super指向的父类空间,所以只有一个子类对象实例。

public class Animal {
    private String name;
    int age;
    private double price;

    public Animal() {
        System.out.println("Animal Created!");
    }

    public Animal(String name) {
        System.out.println("Animal with name Created!");
        setName(name);
    }

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

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

}

public class Dog extends Animal {

    int age;

    public Dog() {
        super("MAKA");
        System.out.println("Dog created!");
        this.age = 233;
        super.age = 666;
    }

    public void speak() {
        System.out.println(getName() + " wang wang!");
    }

}

public class Test {
    public static void main(String[] args) {
        Dog pt = new Dog();
        System.out.println(pt.age);
        pt.speak();
        return;
    }
}
// Animal with name Created!
// Dog created!
// 233
// MAKA wang wang!

属性的继承

父类的所有属性都可以被继承,但是私有属性不能被访问。
当子类和父类有同名属性时,通过super关键字区分,没标注this/super优先访问this的。
image.png

public class Sup {
    int a;
    int b;

}

public class Sub extends Sup {
    int b;

    public void speak() {
        System.out.println("a: " + a);
        System.out.println("this.b: " + this.b);
        System.out.println("super.b: " + super.b);
    }
}

public class Test {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.a = 1;
        sub.b = 2;
        sub.speak();
        return;
    }
}

// a: 1
// this.b: 2
// super.b: 0

方法的覆盖(重写)

当父类提供的方法无法满足子类需求时,对父类相同的方法(包括方法名和参数列表结构完全一致)覆盖。
子类覆盖的方法不能比父类的方法可见性小。父类私有的方法不会被重写(相当于不可见)。
子类返回值类型只能与父类一样或者是父类的子类和null,如果是基本数据类型,需要完全一致。

public class Sup {
    int a;
    int b;

    Sup speak() {
        return new Sub();
    }

    Sub speak(String words) {
        System.out.println("233");
        return new Sub();
    }

}

public class Sub extends Sup {
    int b;

    Sub speak(String words) {
        super.speak(words);
        System.out.println("666");
        return new Sub();
    }
}

public class Test {
    public static void main(String[] args) {
        Sub sub = new Sub();
        sub.a = 1;
        sub.b = 2;
        sub.speak();
        sub.speak("");
        return;
    }
}
// 233
// 666

访问修饰符

image.png

多态

父类引用指向子类对象,产生了多态。(向上转型、里氏替换)
要求有继承关系,多态只能调用父类有的属性、方法,不能调用子类有的。
如果子类有父类方法的重写,则会调用子类的重写方法。
Pet pet = new Dog();
使用场景1:父类作为方法的形参实现多态,使得方法参数类型更宽泛(能用多个子类作为参数类型)

public class Animal {
    private String name;

    public String getName() {
        return name;
    }

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

    public void eat() {
        System.out.println("Animal eat what?");
    }

    public void feed(Animal animal) {
        animal.eat();	// 实际上是调用子类的eat重写方法
    }

}

public class Dog extends Animal {
    public void eat() {
        System.out.println("Dog eat something!");
    }
}

public class Cat extends Animal {
    public void eat() {
        System.out.println("Cat eat fish!");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.eat();
        Animal cat = new Cat();
        cat.eat();
    }
}
// Dog eat something!
// Cat eat fish!

使用场景2:父类作为方法的返回值实现多态,使得方法返回不同的子类对象(能用多个子类作为返回值)

public class Animal {
    private String name;

    public String getName() {
        return name;
    }

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

    public void eat() {
        System.out.println("Animal eat what?");
    }

    public void feed(Animal animal) {
        animal.eat();
    }

    public Animal getAnimal(String type) {
        if (type.equals("Dog"))
            return new Dog();
        if (type.equals("Cat"))
            return new Cat();
        return null;
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal = animal.getAnimal("Cat");
        animal.eat();
    }
}
// Cat eat fish!

向下转型

父类无法调用子类的特有方法,需要强转(向下转型)后才能调用。要保证强转后的子类确实有这个方法。

public class Test {
    public static void main(String[] args) {
        Animal animal = new Cat();
        if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.eatFish(); // Cat独有的方法
        }
    }
}

抽象类

抽象类

不能被实例化的类,因为有些类过于顶层抽象,需要被继承存在才有现实意义,本身并不具备现实意义。使用abstract关键字修饰可以避免这个类被实例化。
常用于被子类继承,提供公共属性和方法;被作为多态的引用。

public abstract class Animal {
    private String name;

    public void eat() {

    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
    }
}

会报错:Cannot instantiate the type Animal

抽象方法

抽象类中的方法本不该被调用(但因为多态,这些方法无法被删去,删了后方法无法被多态调用),而是需要子类实现后再调用子类的重写方法,因此设置为抽象方法。抽象方法只能存在于抽象类(或接口)中。
注意,如果子类也是抽象类,可以实现父类的抽象方法,但非抽象类子类需要实现所有的抽象方法。

public abstract class Animal {
    private String name;
    public abstract void eat();
}

public class Cat extends Animal {
    public void eat() {
        System.out.println("Cat eat fish!");
    }
}

静态成员和静态方法

static关键字修饰的成员或方法,被修饰的称为静态成员(类成员)、静态方法(类方法)。
静态的成员、方法被所有的实例对象共享。
推荐用类名直接访问。
静态方法可以直接通过类名调用。
静态方法优先加载,因此静态方法不能调用非静态方法(未加载)。除非在静态方法中创建了一个类的实例,通过实例引用来调用。
静态方法中同样不能用thissuper,因为此时还没有实例对象。
静态方法不能被重写、没有多态。子类可以调用到父类的静态方法,但这里应不理解为从父类继承的静态方法,而是直接引用了方法。子类与父类重名的静态方法只是独立的方法,而不是重写的方法。
image.png

public class Animal {
    private String name;

    static int count;   // 静态成员变量

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

    public String getName() {
        return name;
    }

    public static int getCount() {		// 静态方法
        return count;
    }

}
public class Test {
    public static void main(String[] args) {
        Animal.count++;		// 通过类访问静态成员
        System.out.println(Animal.getCount());	// 通过类调用类方法

    }
}

举例常用的静态方法Arrays.copyOf()、Arrays.sort()、Math.random()、Math.sqrt()

类加载过程

JVM首次使用某个类时,需通过CLASSPATH来找到该类的.class文件。
找到后将.class文件对类的描述信息加载到内存中,进行保存。(包名、类名、父类、属性、方法、构造方法)
加载时机:

  1. 创建对象
  2. 创建子类对象
  3. 访问静态属性
  4. 调用静态方法
  5. 反射:根据完全限定名主动加载类Class.forName(className)

静态代码块

在类加载时优先执行的代码块,用于预备工作的执行。
类只加载一次,因此只会执行一次静态代码块。
静态代码块中可以使用静态成员和静态方法。

public class Test {
    public static void main(String[] args) {
        new Target();
        new Target();
    }
}

// 静态成员变量被创建str
// 静态代码块执行
// 静态方法被调用
// 动态代码块执行(第二次创建对象时,类已经加载过,就不执行静态代码块内容)
// 构造方法执行
// 动态代码块执行
// 构造方法执行


class Target {
    static String str = "str";
    static {
        System.out.println("静态成员变量被创建" + str);
        System.out.println("静态代码块执行");
        staticMethod();
    }

    {
        System.out.println("动态代码块执行");
    }

    Target() {
        System.out.println("构造方法执行");
    }

    static void staticMethod() {
        System.out.println("静态方法被调用");
    }
}

总结,静态的只属于类空间,非类控件(实例空间)的不允许被使用。

final关键字

final修饰类:不能被继承。
final修饰方法:不能被子类重写。
final修饰属性:不能被重新赋值(常量)。可以直接声明时赋值或者在构造函数/动态代码块中赋值(必须的,如果没有赋初值)。对于静态成员变量用final修饰,可以直接声明时赋值或者在静态代码块中赋值。

class Target {
    final int a;

    static final int b;

    Target() {
        a = 1; // 这里可以
        System.out.println("构造方法执行");
    }

    Target(int x){
        a = x;	// 这里可以
    }

    {
        a = 1;	// 这里可以
        System.out.println("动态代码块执行");

    }
}
class Target {
    static final int b;

    static {
        b = 2;
        System.out.println("静态代码块执行");
    }
}
public class Test {
    public static void main(String[] args) {
        final Target tg = new Target();
        tg = new Target();  // 用final修饰的对象变量不能被重新绑定,
                            // 因为tg在栈中存储的对象地址已经是常量。
    }
}

接口

接口

类似特殊的抽象类,没有构造方法,不能创建对象,只能定义公共静态常量和公共抽象方法。

public interface InterfaceTest {
    public static final double VERSION = 1.0;

    String platform = "Desktop"; // 默认是公共静态常量,可以省略修饰词

    public abstract void test();

    void demo(); // 默认是公共抽象方法,可以省略修饰词
}

public class SubInterface implements InterfaceTest {

    @Override
    public void test() {
        System.out.println("SubInterface-test-method");
    }

    @Override
    public void demo() {
        System.out.println("SubInterface-test-demo");
    }

}

public class Test {
    public static void main(String[] args) {
        InterfaceTest it = new SubInterface();
        it.test();
        it.demo();
    }
}

与抽象类的不同点在于:没有代码块和构造方法、只能有公共静态常量和公共抽象方法。
与抽象类的相同点是:都能作为引用类型实现多态、不能实例化。
在Java 8之后,接口也可以包含默认方法(default修饰的方法)和静态方法(static修饰的方法)。默认方法是在接口中提供默认的方法实现,实现接口的类可以直接继承这个方法。静态方法是在接口中提供的静态方法,可以直接通过接口名调用。

接口表示能力

一个类无法继承多个父类,但可以通过实现多个接口的所有抽象方法(这也是为什么接口中必须是公共的抽象方法)来打到拥有不同能力的效果。
此外接口也能继承,而且是多继承,一个接口可以继承多个接口。

public interface ICode {
    void code();

    void debug();
}
public interface ISport {
    void run();

    void jump();
}
public class Student implements ICode, ISport {

    String can() {
        return "Student can ";
    }

    @Override
    public void run() {
        System.out.println(can() + "run");
    }

    @Override
    public void jump() {
        System.out.println(can() + "jump");
    }

    @Override
    public void code() {
        System.out.println(can() + "code");
    }

    @Override
    public void debug() {
        System.out.println(can() + "debug");
    }

}
public class Test {
    public static void main(String[] args) {
        Student st = new Student();
        st.code();
        st.jump();
    }
}

image.png
常量接口:将多个常用于表示状态或固定值的变量,以静态常量的形式定义在接口中统一管理,提高代码可读性。

接口表示标准

通过统一的标准实现低耦合插件。

package USB;

public interface IUSB {
    void service();
}
package USB;

public class USBFan implements IUSB {
    public void service() {
        System.out.println("A Fan Using USB!");
    }
}
package USB;

public class USBDisk implements IUSB {
    public void service() {
        System.out.println("A Disk Using USB!");
    }
}

package USB;

public class Computer {
    IUSB usb;
}
package USB;

public class test {
    public static void main(String[] args) {
        Computer pc = new Computer();
        USBFan fan = new USBFan();
        // USBDisk disk = new USBDisk();
        pc.usb = fan;	// 即插即用
        pc.usb.service();
    }
}

接口的标记作用

package USB;

import java.io.Serializable;

public class USBDisk implements IUSB,Serializable,Cloneable {
    public void service() {
        System.out.println("A Disk Using USB!");
    }
}

Serializable、Cloneable接口作为标记可以被虚拟机快速检测到,然后决定这对象能否序列化和克隆。

接口回调

为了使用尚未实现的抽象方法,采用接口回调处理。下面的doCall就是采用接口回调的设计。

package callback;

public interface ICallback {
    void call();

    default void doCall() {
        call(); // 为什么没有实现call却可以调用?因为实现类一定会实现!可以假设已经实现了。
    }

}
package callback;

public class RealizeCallback implements ICallback {

    @Override
    public void call() {
        System.out.println("实现了call方法");
    }

}
package callback;

public class RealizeCallback2 implements ICallback {

    @Override
    public void call() {
        System.out.println("实现call的第二个方法");
    }

}
package callback;

public class Test {
    public static void main(String[] args) {
        ICallback cb = new RealizeCallback2();
        cb.doCall();
    }
}

内部类

成员内部类

在一个类的内部再定义一个完整的类。编译之后可生成独立的字节码文件。内部类可直接访问外部类的私有成员,而不破坏封装。可为外部类提供必要的内部功能组件。分为成员内部类、静态内部类、局部内部类、匿名内部类。
成员内部类:

package InnerClass;

public class ClassDemo {

    // 成员内部类
    public class InnerClassDemo {
        public static void InnerStaticMethod() {
            System.out.println("Run InnerStaticMethod");
        }

        public void InnerCommonMethod() {
            System.out.println("Run InnerCommonMethod");
        }
    }

}

import InnerClass.ClassDemo;
import InnerClass.ClassDemo.InnerClassDemo;

public class Test {
    public static void main(String[] args) {
        ClassDemo.InnerClassDemo.InnerStaticMethod();

        ClassDemo out = new ClassDemo();				// 内部类对象实例化:先创建外部类对象,
        InnerClassDemo in = out.new InnerClassDemo(); 	// 再实例化内部类。

        // 或者这样创建
        InnerClassDemo in = new ClassDemo().new InnerClassDemo();
        
        in.InnerCommonMethod();
    }
}

内部类变量命名冲突

package InnerClass;

public class InnerVariable {
    int num = 1;

    public class InnerInnerVariable {
        int num = 2;

        public void InnerMethod() {
            int num = 3;
            System.out.println(num); // 局部变量
            System.out.println(this.num); // 内部类成员变量
            System.out.println(new InnerVariable().num); // 外部类成员变量
            System.out.println(InnerVariable.this.num++); // 外部类成员变量语法
            System.out.println(InnerVariable.this.num); // 仅一个外部类对象,不会像上面多次创建外部类
        }

    }
}

import InnerClass.InnerVariable;
import InnerClass.InnerVariable.InnerInnerVariable;

public class Test {
    public static void main(String[] args) {
        InnerInnerVariable o = new InnerVariable().new InnerInnerVariable();
        o.InnerMethod();

    }
}

私有内部类只能在外部类的内部被创建和访问。这意味着除了外部类之外的任何类都不能直接实例化或访问私有内部类。

package InnerClass;

public class Operator {
    private class AddOP {
        public int add(int a, int b) {
            return a + b;
        }
    }

    public int useAdd(int a, int b) {
        return new AddOP().add(a, b);
    }
}
import InnerClass.Operator;

public class Test {
    public static void main(String[] args) {
        Operator op = new Operator();
        System.out.println(op.useAdd(1, 2));

    }
}

多继承的用法:创建多个内部类,继承自不同父类。

静态内部类

没有静态外部类,只有静态内部类。静态内部类可以包含静态成员,包括静态字段和静态方法。它们可以直接访问外部类的静态成员,但不能直接访问非静态的外部类成员。由于静态内部类不依赖于外部类的实例,因此可以直接使用外部类名来实例化静态内部类,例如:OuterClass.InnerClass inner = new OuterClass.InnerClass();

public class OuterClass {
    private static int staticOuterField = 10;
    private int nonStaticOuterField = 20;

    // 静态内部类
    public static class StaticInnerClass {
        private int staticOuterField = 30;
        public void accessOuterFields() {
            // 访问静态内部类中的同名成员变量
            System.out.println(staticOuterField);
            // 使用外部类名限定访问外部类的同名静态成员变量
            System.out.println(OuterClass.staticOuterField);
            // 静态内部类无法直接访问外部类的非静态成员
            // System.out.println(nonStaticOuterField); // 编译错误
        }
    }

    public static void main(String[] args) {
        // 实例化静态内部类
        StaticInnerClass inner = new StaticInnerClass();
        // 调用静态内部类的方法,访问外部类的静态成员
        inner.accessOuterFields();
    }
}

局部内部类

局部内部类是定义在方法、构造函数或任意代码块内部的内部类。
与成员内部类(在外部类的成员位置定义的内部类)不同,局部内部类的作用域仅限于包含它的代码块内部。
局部内部类可以访问外部类的成员,包括私有成员,并且可以访问其所在方法中的局部变量,但是这些局部变量必须是final或者是等效于final的(即在方法或代码块中不可更改)
因为方法内部不能再定义方法,通过局部内部类的方式创建方法,可以解决这个问题。

public class OuterClass {
    private int outerField = 10;

    public void outerMethod() {
        final int localVar = 20; // 局部变量

        // 定义局部内部类
        class LocalInnerClass {
            public void innerMethod() {
                System.out.println("Accessing outer field: " + outerField);
                System.out.println("Accessing local variable: " + localVar);
            }
        }

        // 实例化局部内部类并调用方法
        LocalInnerClass inner = new LocalInnerClass();
        inner.innerMethod();
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

匿名内部类

匿名内部类是一种特殊的局部内部类,它没有显式的类名。它通常用于在创建对象时直接实现接口或继承类,并覆盖其方法。

package InnerClass;

public interface IUSB {
    void service();
}
import InnerClass.IUSB;

public class Test {
    public static void main(String[] args) {
        IUSB usbmouse = new IUSB() { // IUSB是接口,采用匿名内部类实现实例化
            @Override
            public void service() {
                System.out.println("Using usbmouse");
            }
        };

        usbmouse.service();

    }
}

包装类

在Java中,包装类(Wrapper Class)是一组用于将基本数据类型转换为对象的类。这些包装类提供了许多实用的方法,使得在处理基本数据类型时更加方便和灵活。每个基本数据类型都有对应的包装类。
以下是Java中常见的包装类及其对应的基本数据类型:

  1. Integer:对应int
  2. Long:对应long
  3. Float:对应float
  4. Double:对应double
  5. Boolean:对应boolean
  6. Byte:对应byte
  7. Short:对应short
  8. Character:对应char

这些包装类都位于java.lang包中,并提供了许多有用的方法来处理和转换基本数据类型。例如,可以使用包装类来进行基本数据类型和字符串之间的转换,执行数学运算,以及在集合类中存储基本数据类型的对象等。
以下是一些使用包装类的示例:

// 使用Integer包装类
Integer num1 = new Integer(10); // 创建一个Integer对象
int num2 = num1.intValue(); // 将Integer对象转换为int
String num3 = num1.toString(); // 将Integer对象转换为字符串

// 使用Double包装类
Double d1 = new Double(3.14);
double d2 = d1.doubleValue();
String d3 = d1.toString();

// 使用Boolean包装类
Boolean b1 = new Boolean(true);
boolean b2 = b1.booleanValue();
String b3 = b1.toString();

通过使用包装类,可以在需要对象的地方使用基本数据类型,并在需要基本数据类型的地方使用对象。这提供了更大的灵活性和功能。
装箱(Boxing)和拆箱(Unboxing)是Java中用于在基本数据类型和对应的包装类之间进行转换的过程。
装箱是指将基本数据类型转换为对应的包装类对象。Java提供了自动装箱的特性,使得在需要使用包装类对象的地方,可以直接使用基本数据类型,编译器会自动将其转换为对应的包装类对象。
拆箱是指将包装类对象转换为对应的基本数据类型。同样,Java也提供了自动拆箱的特性,使得在需要使用基本数据类型的地方,可以直接使用包装类对象,编译器会自动将其转换为对应的基本数据类型。
以下是装箱和拆箱的示例:

// 装箱
int num1 = 10;
Integer num2 = num1; // 自动装箱

// 拆箱
Integer num3 = new Integer(20);
int num4 = num3; // 自动拆箱

包装类的缓存
在Java中,Integer类维护了一个缓存,其中默认缓存了范围为-128到127之间的整数对象。也就是说,当使用装箱操作创建一个Integer对象时,如果该对象的值在-128到127之间,会直接返回缓存中的对象,而不会创建新的对象。

Integer num1 = 10;
Integer num2 = 10;
System.out.println(num1 == num2); // 输出 true

Integer num3 = 128;
Integer num4 = 128;
System.out.println(num3 == num4); // 输出 false

常用类

String类

public class Test {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = new String("abc");
        System.out.println(s1 == s2);// true
        System.out.println(s1 == s3);// false 地址不同
        System.out.println(s3 == s4);// false
        System.out.println(s1.equals(s2));// true equals逐位比较字符串
        System.out.println(s1.equals(s3));// true
        System.out.println(s3.equals(s4));// true
    }
}

在Java中,字符串常量池(String Pool)是一个特殊的内存区域,用于存储字符串常量。当使用字符串字面量(如"abc")创建字符串对象时,如果字符串常量池中已经存在相同内容的字符串,就会直接返回该字符串的引用,而不会创建新的对象。
需要注意的是,如果使用显式的构造函数(如new String(“abc”))创建字符串对象,即使内容相同,也会创建一个新的对象,而不是使用字符串常量池中的对象。这是因为使用显式的构造函数会强制创建新的对象,而不考虑字符串常量池的机制。
因此,在比较字符串时,应该使用equals()方法而不是操作符,因为equals()方法会比较字符串的内容,而操作符比较的是引用是否相等。
String类的常用方法

  1. length():返回字符串的长度(字符数)。
  2. charAt(int index):返回指定索引位置处的字符。
  3. substring(int beginIndex, int endIndex):返回从指定的开始索引位置到结束索引位置之前的子字符串。
  4. equals(Object obj):比较字符串与指定对象是否相等。
  5. toLowerCase():将字符串转换为小写。
  6. toUpperCase():将字符串转换为大写。
  7. trim():去除字符串开头和结尾的空格。
  8. startsWith(String prefix):检查字符串是否以指定的前缀开始。
  9. endsWith(String suffix):检查字符串是否以指定的后缀结尾。
  10. contains(CharSequence sequence):检查字符串是否包含指定的字符序列。
  11. replace(char oldChar, char newChar):将字符串中的所有旧字符替换为新字符。
  12. split(String regex):将字符串拆分为子字符串数组,使用指定的正则表达式作为分隔符。
  13. indexOf(int ch):返回指定字符第一次出现的索引。
  14. lastIndexOf(int ch):返回指定字符最后一次出现的索引。
  15. concat(String str):将指定字符串连接到原字符串的末尾。
  16. isEmpty():检查字符串是否为空。

StringBuffer
StringBuffer是Java中可变的字符串类,它与String类相比具有以下特点:
可变性:StringBuffer对象是可变的,这意味着可以在原始字符串的基础上进行修改,向其添加、插入、删除字符等操作,而不会创建新的对象。这在需要频繁修改字符串内容时非常有用。
线程安全:StringBuffer类是线程安全的,它的方法在多线程环境下可以同步访问和修改字符串内容,确保线程安全性。这是通过内部使用同步机制(synchronized)实现的。然而,由于同步机制的存在,它的性能可能比StringBuilder类稍低。
性能较低:相对于StringBuilder类,StringBuffer的性能稍低。这是因为StringBuffer的方法都是同步的,而同步机制引入了一些额外的开销。因此,在单线程环境下,如果不需要线程安全性,推荐使用StringBuilder类,因为它更高效。

StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" ");
sb.append("World!");
String result = sb.toString();
System.out.println(result); // 输出: Hello World!

Object类的方法

任何类包括java的接口都直接或间接继承了Object。
实例方法:

  1. getClass() 获取对象实例的类型
public class Animal {

}
public class Cat extends Animal {

}
public class Main {
    public static void main(String[] args) {
        Animal cat1 = new Animal();
        Animal cat2 = new Cat();
        System.out.println(cat1.getClass());
        System.out.println(cat2.getClass());
        System.out.println(cat2.getClass().getSimpleName());
    }
}
// class Animal
// class Cat
// Cat
  1. hashCode() 这个hash值是地址的映射,但存在哈希冲突,会有不同的地址值映射到同一个哈希值。
package Object;

public class Test {
    public static void main(String[] args) {
        Animal cat1 = new Cat();
        Cat cat2 = (Cat) cat1;
        System.out.println(cat1.hashCode() + " -- " + cat2.hashCode());
    }
}
// 149928006 -- 149928006
package Object;

public class Test {
    public static void main(String[] args) {
        System.out.println(new Animal().hashCode());
        System.out.println(new Animal().hashCode());

        String s1 = new String("test");
        String s2 = "test";
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());  // 重写hash值,使两个一样的字符串值相同

        System.out.println(System.identityHashCode(s1));    
        System.out.println(System.identityHashCode(s2));    // 读取未被重写的hash值
    }
}

// 41359092
// 149928006
// 3556498
// 3556498
// 713338599
// 168423058
  1. equals() 返回两个对象是否是统一对象
package Object;

public class Animal {

    String name;

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

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj.hashCode() == this.hashCode())
            return true;
        return false;
    }
}

package Object;

public class Test {
    public static void main(String[] args) {
        Animal a = new Animal("cat");
        Animal b = new Animal("cat");
        System.out.println(a.hashCode());
        System.out.println(b.hashCode());
        System.out.println(a.equals(b));
    }
}

// 98262
// 98262
// true

重写了两个方法,让原本不能equals的对象,在满足name相同时就相等。

  1. clone() 拷贝对象,浅拷贝。需要实现Cloneable接口,调用super.clone()
package Object;

public class Animal implements Cloneable {

    String name;

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

    public Animal clone() {
        Animal an = null;
        try {
            an = (Animal) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return an;
    }

}

package Object;

public class Test {
    public static void main(String[] args) {
        Animal a = new Animal("cat");
        Animal b = a.clone();
        System.out.println(b.name);
    }
}
// cat

日期与系统类

在Java中,Date类用于表示日期和时间。以下是一些常用的Date类方法以及相应的示例:

  1. toString():将Date对象转换为表示日期和时间的字符串。
Date date = new Date();
String str = date.toString();
System.out.println(str); // 输出当前日期和时间的字符串表示
  1. getTime():返回自1970年1月1日以来的毫秒数。
Date date = new Date();
long timeMillis = date.getTime();
System.out.println(timeMillis); // 输出当前时间的毫秒数
  1. before(Date when):检查当前日期是否在指定日期之前。
Date current = new Date();
Date future = new Date(2022, 0, 1); // 假设指定的日期是2022年1月1日
boolean isBefore = current.before(future);
System.out.println(isBefore); // 输出true或false,取决于当前日期是否在指定日期之前
  1. after(Date when):检查当前日期是否在指定日期之后。
Date current = new Date();
Date past = new Date(2020, 0, 1); // 假设指定的日期是2020年1月1日
boolean isAfter = current.after(past);
System.out.println(isAfter); // 输出true或false,取决于当前日期是否在指定日期之后
  1. equals(Object obj):比较两个Date对象是否相等。
Date date1 = new Date();
Date date2 = new Date();
boolean isEqual = date1.equals(date2);
System.out.println(isEqual); // 输出true或false,取决于两个Date对象是否相等
  1. compareTo(Date anotherDate):将当前Date对象与另一个Date对象进行比较。
Date date1 = new Date();
Date date2 = new Date(2023, 0, 1); // 假设另一个Date对象是2023年1月1日
int result = date1.compareTo(date2);
System.out.println(result); // 输出负数、零或正数,表示当前Date对象在另一个Date对象之前、相同或之后

这些方法是Date类中一些常用的方法。然而,需要注意的是,Java 8及以后的版本中推荐使用java.time包中的日期和时间类,如LocalDateLocalDateTime,以获得更丰富的日期和时间操作功能。
格式化日期

import java.text.SimpleDateFormat;
import java.util.Date;

Date currentDate = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = formatter.format(currentDate);
System.out.println(formattedDate);

常用System类

public class SystemExample {
    public static void main(String[] args) {
        // 获取当前时间的毫秒数
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println("当前时间的毫秒数:" + currentTimeMillis);

        // 获取当前系统的环境变量
        String[] envVariables = System.getenv().entrySet().toArray(new String[0]);
        System.out.println("当前系统的环境变量:");
        for (String envVariable : envVariables) {
            System.out.println(envVariable);
        }

        // 获取指定键的系统属性
        String javaVersion = System.getProperty("java.version");
        System.out.println("Java版本:" + javaVersion);

        // 创建源数组并赋值
        int[] sourceArray = {1, 2, 3, 4, 5};
        int[] destinationArray = new int[5];

        // 复制数组
        System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);

        // 输出目标数组
        for (int num : destinationArray) {
            System.out.print(num + " ");
        }

        // 执行垃圾回收
        System.gc();

        // 中止当前正在运行的Java虚拟机
        System.exit(0);
    }
}

Random类

Java中的Random类是一个用于生成伪随机数的工具类。它可以用于生成不同类型的随机数,如整数、浮点数、布尔值等。Random类位于java.util包中,使用时需要先导入该类。
下面是Random类的一些常用方法及其说明:

  1. nextInt(int bound):生成一个介于0(包括)和指定边界值(不包括)之间的随机整数。
import java.util.Random;

Random random = new Random();
int randomNumber = random.nextInt(10); // 生成0到9之间的随机整数
System.out.println(randomNumber);
  1. nextDouble():生成一个介于0.0(包括)和1.0(不包括)之间的随机浮点数。
import java.util.Random;

Random random = new Random();
double randomValue = random.nextDouble(); // 生成0.0到1.0之间的随机浮点数
System.out.println(randomValue);
  1. nextBoolean():生成一个随机的布尔值。
import java.util.Random;

Random random = new Random();
boolean randomBoolean = random.nextBoolean(); // 随机生成一个布尔值
System.out.println(randomBoolean);
  1. nextBytes(byte[] bytes):生成随机的字节数组。
import java.util.Random;

Random random = new Random();
byte[] randomBytes = new byte[5];
random.nextBytes(randomBytes); // 生成随机的字节数组
System.out.println(Arrays.toString(randomBytes));

这些只是Random类中的一些常用方法。你可以根据需要使用其他方法来生成不同类型的随机数。
需要注意的是,Random类生成的是伪随机数,它们是通过一个初始种子计算得到的。如果你在相同的种子下创建了相同的Random实例,它们将生成相同的随机数序列。如果需要更高质量的随机数,可以考虑使用java.security.SecureRandom类。

正则表达式

是的,下面是一份常见的正则表达式规则表,列出了常用的元字符和模式:

元字符描述
.匹配除换行符以外的任意字符。
\\w匹配任意字母、数字或下划线。等效于 [a-zA-Z0-9_]
\\d匹配任意数字。等效于 [0-9]
\\s匹配任意空白字符,包括空格、制表符、换行符等。
\\b匹配单词的边界。
^匹配输入字符串的开始位置。
$匹配输入字符串的结束位置。
[]匹配括号内的任意一个字符。
[^]匹配除括号内字符之外的任意一个字符。
*匹配前面的元素零次或多次。
+匹配前面的元素一次或多次。
?匹配前面的元素零次或一次。
{n}匹配前面的元素恰好 n 次。
{n,}匹配前面的元素至少 n 次。
{n,m}匹配前面的元素至少 n 次,但不超过 m 次。
()创建捕获组,用于分组和提取子字符串。
  • 15
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值