==
判断基本数据类型时,比较的是值
判断对象类型时,两个对象是否指向同一内存地址,和hashCode没有直接关系,只是恰好Object及其继承类的hashCode()方法返回的是内存地址
OverRideHashCode a = new OverRideHashCode(1);
OverRideHashCode b = new OverRideHashCode(1);
System.out.println(a == b); //false
System.out.println(a.hashCode()); //118 虽然哈希码相同但是内存地址不同
System.out.println(b.hashCode()); //118
hashCode()
hashCode()只是特征表示码,表示两个对象有同样的特征值,主要用来配合哈希表使用
调用hashCode方法默认返回的值被称为identity hash code(标识哈希码),接下来我们会用标识哈希码来区分重写hashCode方法。如果一个类重写了hashCode方法,那么通过调用System.identityHashCode(Object o)方法获得标识哈希码。
**Q:**hashCode()方法返回的到底是不是对象地址?
A: 不一定,hashCode()是一个native方法,即非java实现的(c实现),有多种实现策略,在不同的jvm里有不同的实现,可能是内存地址,也有可能是通过当前状态值进行异或(XOR)运算得到的一个 hash 值
可以通过配置jvm启动参数 -XX:hashCode=N N可以是0/1/2/3/4/5
时,重写了hashCode()和equals(),导致内部值相同时,两个对象hashcode也相同,equals也成立
equals
如果没有重写的话,比如在Object中,equals等效与==
如果重写了,比如在String中,equals表示一种逻辑上的相等,比较的是字符是否相同,与==不等效
hashCode和equals两个方法是有语义关联的,它们需要满足:
A.equals(B)==true ---> A.hashCode()==B.hashCode()
注意,如果重写了equals,必须要重写hashCode,保证equals认定为两个相同的对象,具有相同哈希值,保证逻辑上的一致性
构造方法
概述
类创建实例时,通过构造方法初始化,构造方法名字和类名一样,可以任意传参,内部语句也可以任意编写,但是没有返回值(没有void)
public FormBiz(RemoteActivitiService remoteActivitiService, RemoteTaskService remoteTaskService, IWbAppService appService, IWbAppManagerService wbAppManagerService, IWbOrgDeptService deptService, IWbDynamicFormService dynamicFormService, IWbDynamicFormDirectoryService dynamicFormDirectoryService, DynamicDataSource dynamicDataSource, FormPermissionProvider formPermissionProvider, FormManager formManager, UserProvider userProvider) {
this.remoteActivitiService = remoteActivitiService;
this.remoteTaskService = remoteTaskService;
this.appService = appService;
this.wbAppManagerService = wbAppManagerService;
this.deptService = deptService;
this.dynamicFormService = dynamicFormService;
this.dynamicFormDirectoryService = dynamicFormDirectoryService;
this.dynamicDataSource = dynamicDataSource;
this.formPermissionProvider = formPermissionProvider;
this.formManager = formManager;
this.userProvider = userProvider;
}
默认构造方法
如果一个类没有定义构造方法,那么会生成一个默认无参的构造方法,如果自定义构造方法,就会覆盖无参的方法
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false; 也可以对字段进行初始化
class Person {
private String name = "Unamed";
private int age = 10;
}
赋值时,先执行初始化赋值,再执行构造方法赋值
方法重载
概述
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
int indexOf(int ch):根据字符的Unicode码查找;int indexOf(String str):根据字符串查找;int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。]
继承
概述
定义Person类
class Person {
private String name;
private int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
定义Student继承于Person
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
public int getScore() { … }
public void setScore(int score) { … }
}
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
lombok @AllArgsConstructor 无法获取全部父类构造器
继承树
注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。下图是Person、Student的继承树:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Person │
└───────────┘
▲
│
┌───────────┐
│ Student │
└───────────┘
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
类似的,如果我们定义一个继承自Person的Teacher,它们的继承树关系如下:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Person │
└───────────┘
▲ ▲
│ │
│ │
┌───────────┐ ┌───────────┐
│ Student │ │ Teacher │
└───────────┘ └───────────┘
protected
继承有个特点,就是子类无法访问父类的private字段或者private方法。例如,Student类就无法访问Person类的name和age字段:
class Person {
private String name;
private int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // 编译错误:无法访问name字段
}
}
这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问:
class Person {
protected String name;
protected int age;
}
class Student extends Person {
public String hello() {
return "Hello, " + name; // OK!
}
}
因此,protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问,后面我们还会详细讲解。
super
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。例如:
class Student extends Person {
public String hello() {
return "Hello, " + super.name;
}
}
实际上,这里使用super.name,或者this.name,或者name,效果都是一样的。编译器会自动定位到父类的name字段。
但是,在某些时候,就必须使用super。我们来看一个例子:
// super
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
this.score = score;
}
}
Run
运行上面的代码,会得到一个编译错误,大意是在Student的构造方法中,无法调用Person的构造方法。
这是因为在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();,所以,Student类的构造方法实际上是这样:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(); // 自动调用父类的构造方法
this.score = score;
}
}
但是,Person类并没有无参数的构造方法,因此,编译失败。
解决方法是调用Person类存在的某个构造方法。例如:
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
这样就可以正常编译了!
因此我们得出结论:如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。
这里还顺带引出了另一个问题:即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
阻止继承
正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。
例如,定义一个Shape类:
public sealed class Shape permits Rect, Circle, Triangle {
...
}
上述Shape类就是一个sealed类,它只允许指定的3个类继承它。如果写:
public final class Rect extends Shape {...}
是没问题的,因为Rect出现在Shape的permits列表中。但是,如果定义一个Ellipse就会报错:
public final class Ellipse extends Shape {...}
// Compile error: class is not allowed to extend sealed class: Shape
原因是Ellipse并未出现在Shape的permits列表中。这种sealed类主要用于一些框架,防止继承被滥用。
sealed类在Java 15中目前是预览状态,要启用它,必须使用参数--enable-preview和--source 15。
向上转型
如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:
Student s = new Student();