Hello World

Java基础

2025/03/02
loading

字符窜

任何一个Java对象都可以转换成字符串。
String类没有提供方法来修改字符串中的某个字符,所以String类对象是不可变的,但我们可以修改字符串变量,让它指向另外的字符串。
不可变字符串有其优点:编译器可以让字符串共享。想象各个字符串存放在一个公共存储池中。

用equals方法检测两个字符串是否相等,equalsIgnoreCase方法忽略大小写判断是否相等。

构建字符串

StringBuilder类可以构建字符串,先构建一个空的字符串构建器StringBuilder builder = new StringBuilder();,需要添加字符串时,调用append方法,字符串构建完成时,调用toString方法。
StringBuffer类的效率不如StringBuilder类,但StringBuffer允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常如此),则使用StringBuilder类。

数组

for each循环

1
2
3
for (variable: collection) statement
for (int element: a)
System.out.println();

collection表达式必须是一个数组或者是一个实现了Iterable接口的类对象

数组拷贝

int[] a = b; 此时,两个变量引用同一个数组。
要将数组中的所有值拷贝到一个新数组中,使用Arrays类的copyOf方法,其中第二个参数为新数组长度。该方法常用于增加数组的大小。

日期

LocalDate、LocalTime、LocalDateTimeZoned使用参考https://blog.csdn.net/qq_33697094/article/details/115454081
Date类表示时间点,LocalDate类日历表示法

对象与泪

面向对象程序设计概述

面向对象程序设计OOP,是主流的程序设计范型。面向对象的程序由对象组成,每个对象包含对用户公开的特定功能和隐藏的实现。
类指定了如何构造对象。将类想象成模具,对象想象成模具制造出的成品。
由一个类构造contract对象的过程称为创建这个类的一个实例instance。

封装,有时称为信息隐藏。是将数据和行为组合在一个包中,对使用者隐藏具体的实现细节。
对象中的数据称为实例字段,操作数据的过程称为方法。
实现封装的关键在于,绝对不能让其他类中的方法直接访问这个类的实例字段。程序只能通过对象的方法与对象数据进行交互。封装为对象赋予了“黑盒”特征。

面向对象程序设计的另一个原则让用户自定义Java类变得更容易:可以通过扩展其他类来构建新类。这个新类具有被扩展的那个类的全部属性和方法。所有类都扩展自一个“神通广大的超类”Object类。

识别类的一个简单经验是在分析问题的过程中寻找名词,而方法对应动词。
类之间的关系有依赖、聚合和继承等。
依赖指一个类的方法要使用或操作另一个类的对象。应当尽可能减少相互依赖的类。这里的关键是,如果类A不知道B的存在,它就不会关心B的任何改变(这意味着B的改变不会在A中引入bug)

采用UML统一建模语言来绘制类图,来描述类之间的关系

使用预定义的类

要想使用对象,首先必须构造对象,并指定其初始状态。然后对对象应用方法。
使用构造器或称构造函数来构造新实例。构造器是一种特殊的方法,作用是构造并初始化对象。构造器总是与类同名。以Date类为例,构造Date对象,在构造器前面加上new:new Date(); ,可以将这个对象存放在一个变量中:Date rightNow = new Date();
对象变量和对象有区别,对象变量并不实际包含一个对象,它只是引用一个对象。
任何对象变量的值都是一个引用,指向存储在另一个地方的某个对象。new操作符的返回值也是一个引用。(注:对象变量应该可以称作对象引用,应该是一个东西,引用可以比作快捷方式,引用指向内存中的实际对象)
可以把Java里面的对象变量看作C++中的对象指针。
所有的Java对象都存储在堆中。

自定义类

文件名必须与public类的名字匹配。一个源文件中只能有一个公共类,但可以有任意数目的非公共类。
构造器与类同名,构造器会将实例字段初始化为所希望的初始状态。
构造器总是结合new操作符来调用。
注意在所有方法中都不要使用与实例字段同名的变量。
Java10中,如果可以从变量的初始值推导出它们的类型,则可以用var声明局部变量,注意只能用于方法中的局部变量。

定义一个类时,最好清楚地知道哪些字段可能为null。如果有的字段可能为null,宽容的解决方案是使用Objects.requireNonNullElse方法,将null转换为适当的非null值,严格的解决方案是使用Objects.requireNonNull方法,如果用null构造对象,会产生NullPointerException异常。

可以将实例字段定义为final,这样的字段必须在构造对象时初始化,并且以后不能再修改这个字段。比如可以将String类型的字段设置为final:private final String name;。对于可变类,例如StringBuilder:private final StringBuilder name;在构造器中,name = new StringBuilder(),final只是表示存储在a变量中的对象引用不会再指示另一个不同的StringBuilder对象,不过这个对象还是可以更改的。

静态方法是不操作对象的方法,例如Math.pow(),它并不使用任何Math对象来完成这个任务。
静态方法可以访问静态字段。
下面两种方法可以使用静态方法:1. 方法不需要访问对象状态,因为它需要的所有参数都通过显式参数提供。2. 方法只需要访问类的静态字段。

工厂方法,类似LocalDate的类使用静态工厂方法来构造对象,不使用构造器创建对象的原因:1. 无法为构造器命名,构造器名字需要和类相同,有时候需要有两个不同的名字,构造不同的对象。2. 使用构造器时,无法改变所构造对象的类型。

main方法也是一个静态方法,每一个类都可以有一个main方法。

按值调用表示方法接收的是调用者提供的值。
按引用调用表示方法接收的是调用者提供的变量位置。
Java总是采用按值调用,方法会得到所有参数值的一个副本。如果给方法传递一个基本数据类型,则方法不能改变其值,如果给方法传递一个对象(引用),则方法会复制一个对象引用,这两个引用同时引用(指向)同一个对象,此时可以修改对象,使得原来传递进去的对象引用所引用的对象发生改变。

对象构造,Java提供了多种编写构造器的机制

  1. 重载,多个方法有相同的方法名但有不同的参数。编译器将参数类型进行匹配,查找匹配的过程称为重载解析
  2. 默认字段初始化,如果在构造器中没有显式地为一个字段设置初始值,就会自动设置为默认值(0,false,null)
  3. 无参数的构造器,对象的状态会设置为适当的默认值。如果类中没有构造器,就会得到一个默认的无参数构造器,该构造器将所有的实例字段设置为默认值。
  4. 显式字段初始化,在类定义中直接为字段赋值。初始值不一定是常量,也可以利用方法返回值来初始化字段。
  5. 参数名,编写很小的构造器时,参数名可以设置为字段名前面加一个a,比如,在构造器中:name = aName;,或者将参数名设置与字段名一致,初始化时:this.salary = salary;
  6. 调用另一个构造器,如果构造器的第一个语句形如this(...),这个构造器将调用同一个类的另一个构造器。
  7. 初始化块,不常用。静态初始化块,在初始化块前加static。
  8. 对象析构与finalize方法,

记录record,是一种特殊形式的类,其状态不可变,而且公共可读。record Point(double x, double y) {}
记录的实例字段称为组件,这个类有一个构造器Point(double x, double y)和两个访问器:public double x(); 和 public double y()。每个记录会有三个自动定义的方法:toString、equals和hashCode。
可以在记录中编写方法,记录可以有静态字段和方法,但是不能为记录增加实例字段
记录的实例字段(上面的x和y)自动为final字段,不过它们可以是可变对象的引用,这样记录实例是可变的。
对于完全由一组变量表示的不可变数据,要使用记录而不是类。

标准构造器p136没看懂

使用包将类组织在一个集合中。使用包的主要原因是确保类名的唯一性。
对编译器来说,嵌套的包之间没有任何关系,都是相互独立的包。
一个类可以使用所属包的所有类,以及其他包的公共类。

标记为public的部分可以由任意类使用,标记为private的部分只能由定义它们的类使用,没有指定的话,这个部分(类、方法或变量)可以由同一个包中的所有方法访问。

p144-154,类路径,jar文件,文档注释。以后再看

类设计技巧:

  1. 保证数据私有
  2. 初始化数据
  3. 不要使用过多的基本数据类型
  4. 不是所有字段需要getter和setter
  5. 分解有过多职责的类
  6. 类名和方法名要能体现其职责
  7. 优先使用不可变的类

继承

继承的基本思想是基于已有的类创建新的类。

类 子类 超累

is-a关系是继承的一个明显特征。使用extends表示继承。超类和子类来源于集合中的术语,超类是子类集合的超集(想象Venn图中大圈是超集,小圈是子集)。
扩展超类来定义子类,只需要指出子类与超类的不同之处,将最一般的方法放在超类中,将特殊的方法放在子类中。
private私有字段不会被继承,也就是说子类不能访问超类的私有字段。

虽然这些字段在内存中存在于子类对象中,但从代码访问权限的角度来看,子类没有获得对这些字段的访问权,因此在某种意义上没有”继承”到这些字段的使用权。
如果Parent类提供了public或protected的getter/setter方法来访问这些私有字段,Child类就可以通过这些方法间接访问这些字段。

覆盖方法

在覆盖方法时如果需要获取超类中的某个私有字段,需要使用super.get字段名();来获取。super是指示编译器调用超类方法的特殊关键字。

子类构造器

super();由于子类的构造器不能访问超类的私有字段,所以需要通过构造器来初始化这些私有字段。使用super调用构造器的语句必须是子类构造器里面的第一条语句。如果构造子类对象时没有显式地调用超类的构造器,那么超类必须有一个无参的构造器,这个构造器会在子类构造前调用。

一个对象变量可以指示多种实际类型,称为多态。运行时能够自动地选择适当的方法,称为动态绑定。

多态

is-a规则可以判断是否应该将数据设计为继承关系,该规则指出子类的每个对象也是超类的对象。
is-a规则的另一种表述是替换原则,指程序中需要超类对象的任何地方都可以使用子类对象替换。
可以将子类对象赋给超类变量。
一个Employee类型的变量既可以引用Employee类型的对象,也可以引用Employee类的任何一个子类的对象。但这个变量不能调用子类的方法。
不能将超类的引用赋给子类变量。

理解方法调用

在对象上应用方法调用,首先编译器需要了解所有可能要调用的候选方法,然后根据提供的参数类型判断调用可能的方法,注意调用时允许类型转换。
如果是private、static、final方法或者构造器,那么编译器可以准确地知道应该调用哪个方法,这称为静态绑定。与此对应的,如果要调用的方法依赖于隐式参数的实际类型,则必须在运行时使用动态绑定。(此处解释:对于private、static、final方法和构造器,这些东西不能被继承,所以可以在编译时确定。但对于其他方法,由于可以被继承,运行时才能确定该调用哪个方法)
虚拟机预先为每个类计算了一个方法表。
动态绑定有一个非常重要的特性:无须修改现有的代码就可以对程序进行扩展。

在覆盖方法时,子类方法不能低于超类方法的可见性,超类方法为public,子类方法必须是public。

阻止继承:final类和方法

用final修饰类,表示不允许继承该类。final修饰方法,表示不允许子类覆盖该方法。
将一个类声明为final,类里面的所有方法自动称为final方法,字段不会成为final。

强制类型转换,有些没看懂p170

进行强制类型转换的唯一原因是:要在暂时忘记对象的实际类型之后使用对象的全部功能。
将一个值存入一个变量时,编译器将检查你是否承诺过多。如果将一个子类引用赋给一个超类变量,Employee e = new Manager();,此时承诺较少,编译器允许。

详细说明:
场景 1️⃣:向上转型(子类→超类)
Employee e = new Manager(); // 子类赋给超类变量

  • 发生了什么Manager 对象被当作 Employee 使用。
  • 编译器检查:允许,因为子类对象 必定满足超类的所有能力
  • 承诺过少:此时变量 e 的声明类型是 Employee,编译器只允许调用 Employee 的方法(比如 e.work()),即使实际对象是 Manager(可能有 e.manageTeam() 方法),编译器也会隐藏子类的额外能力。
    ✅ 安全:没有“承诺过多”,因为实际对象的能力 >= 变量声明的能力。
    场景 2️⃣:向下转型(超类→子类)
    Employee e = new Employee(); // 普通员工
    Manager m = (Manager) e; // 强制转换!
  • 发生了什么:试图将普通 Employee 当作 Manager 使用。
  • 编译器检查:允许编译(因为语法上可能成立),但 运行时崩溃ClassCastException)。
  • 承诺过多:变量 m 的声明类型是 Manager,承诺可以调用 manageTeam() 等方法,但实际对象只是一个普通 Employee,没有这些能力。
    ❌ 危险:实际对象的能力 < 变量声明的能力,导致“承诺过多”。

进行强制类型转换前,先查看能否成功地转换,使用instanceof。

instanceof模式匹配 没看懂p172

受保护访问

protected,受保护字段只能由同一个包中的类访问。谨慎使用受保护的字段。受保护的方法更有意义。

Object:所有类的超类

每个类都扩展了Object

Object类型的变量

可以使用Object类型的变量引用任何类型的对象。
Java中,只有基本类型不是对象。

equals方法

Object类中实现的equals方法将确定两个对象引用是否相同。有的时候需要基于状态检测对象的相等性。
比较对象引用是否相同类似于比较快捷方式是否指向同一个位置。

在子类中定义equals方法时,首先手动调用超类的equals

相等测试与继承 没看懂p176

hashcode方法

toString方法

泛型数组列表ArrayList

是一个有类型参数的泛型类。与数组类似,但在添加或删除元素时,可以自动调整容量。
声明ArrayList:ArrayList<Employee> staff = new ArrayList<Employee>();
Java10中,可以使用var关键字声明。如果没有var,可以省略右边尖括号里面的Employee。
用add方法添加元素。ArrayList管理一个内部的对象引用数组,如果数组满了,则会自动创建一个更大的数组,然后将所有对象拷贝过去。
如果能够估计出可能存储的元素数量,可以用ensureCapacity方法提前分配空间,比如提前分配一个比较大的空间,这样就不用重复的扩容。
也可以在ArrayList构造器中指定初始容量:ArrayList<Employee> staff = new ArrayList<>(100)
size方法返回实际元素个数。一旦确定大小固定,可以用trimToSize方法,垃圾回收器将会回收多余的存储空间。
使用get和set方法访问和修改ArrayList中的元素。toArray方法将元素复制到一个数组中。
用add和remove方法可以在指定位置添加和删除元素,类似于数据结构中的线性表,在中间增删元素会移动后面的元素。

类型化与原始数组列表的兼容性 p192 感觉暂时不用看

对象包装器与自动装箱

有时需要将int这类基本类型转换为对象,所有基本类型都有对应的类,称为包装器。包装器类是不可变的。
定义ArrayList时,尖括号中不能是int,需要写成Integer。这样每个值都存在一个对象中,所以ArrayList<Integer>的效率远低于int[]数组。
ArrayList<Integer>添加元素时,可以直接用add(1),会自动转换成add(Integer.valueOf(1)),称为自动装箱。

反射

反射是指在程序运行期间更多地了解类及其属性的能力。

CATALOG
  1. 1. 字符窜
    1. 1.1. 构建字符串
  2. 2. 数组
    1. 2.1. for each循环
    2. 2.2. 数组拷贝
  3. 3. 日期
  4. 4. 对象与泪
    1. 4.1. 面向对象程序设计概述
    2. 4.2. 使用预定义的类
    3. 4.3. 自定义类
  5. 5. 继承
    1. 5.1. 类 子类 超累
      1. 5.1.1. 覆盖方法
      2. 5.1.2. 子类构造器
      3. 5.1.3. 多态
      4. 5.1.4. 理解方法调用
      5. 5.1.5. 阻止继承:final类和方法
      6. 5.1.6. 强制类型转换,有些没看懂p170
      7. 5.1.7. instanceof模式匹配 没看懂p172
      8. 5.1.8. 受保护访问
    2. 5.2. Object:所有类的超类
      1. 5.2.1. Object类型的变量
      2. 5.2.2. equals方法
      3. 5.2.3. 相等测试与继承 没看懂p176
      4. 5.2.4. hashcode方法
      5. 5.2.5. toString方法
    3. 5.3. 泛型数组列表ArrayList
    4. 5.4. 对象包装器与自动装箱
    5. 5.5. 反射