字符窜
任何一个Java对象都可以转换成字符串。
String类没有提供方法来修改字符串中的某个字符,所以String类对象是不可变的,但我们可以修改字符串变量,让它指向另外的字符串。
不可变字符串有其优点:编译器可以让字符串共享。想象各个字符串存放在一个公共存储池中。
用equals方法检测两个字符串是否相等,equalsIgnoreCase方法忽略大小写判断是否相等。
构建字符串
StringBuilder类可以构建字符串,先构建一个空的字符串构建器StringBuilder builder = new StringBuilder();
,需要添加字符串时,调用append方法,字符串构建完成时,调用toString方法。
StringBuffer类的效率不如StringBuilder类,但StringBuffer允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常如此),则使用StringBuilder类。
数组
for each循环
1 |
|
collection表达式必须是一个数组或者是一个实现了Iterable接口的类对象
数组拷贝
int[] a = b;
此时,两个变量引用同一个数组。
要将数组中的所有值拷贝到一个新数组中,使用Arrays类的copyOf方法,其中第二个参数为新数组长度。该方法常用于增加数组的大小。
日期
LocalDate、LocalTime、LocalDateTime 和 Zoned使用参考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提供了多种编写构造器的机制
- 重载,多个方法有相同的方法名但有不同的参数。编译器将参数类型进行匹配,查找匹配的过程称为重载解析
- 默认字段初始化,如果在构造器中没有显式地为一个字段设置初始值,就会自动设置为默认值(0,false,null)
- 无参数的构造器,对象的状态会设置为适当的默认值。如果类中没有构造器,就会得到一个默认的无参数构造器,该构造器将所有的实例字段设置为默认值。
- 显式字段初始化,在类定义中直接为字段赋值。初始值不一定是常量,也可以利用方法返回值来初始化字段。
- 参数名,编写很小的构造器时,参数名可以设置为字段名前面加一个a,比如,在构造器中:
name = aName;
,或者将参数名设置与字段名一致,初始化时:this.salary = salary;
- 调用另一个构造器,如果构造器的第一个语句形如
this(...)
,这个构造器将调用同一个类的另一个构造器。 - 初始化块,不常用。静态初始化块,在初始化块前加static。
- 对象析构与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文件,文档注释。以后再看
类设计技巧:
- 保证数据私有
- 初始化数据
- 不要使用过多的基本数据类型
- 不是所有字段需要getter和setter
- 分解有过多职责的类
- 类名和方法名要能体现其职责
- 优先使用不可变的类
继承
继承的基本思想是基于已有的类创建新的类。
类 子类 超累
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))
,称为自动装箱。
反射
反射是指在程序运行期间更多地了解类及其属性的能力。