123

126 阅读7分钟

==

判断基本数据类型时,比较的是值

判断对象类型时,两个对象是否指向同一内存地址,和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

![image-20211125145808108](面向对象基础.assets/image-20211125145808108.pngimage-20211125150343680Object里的hashCode默认为内存地址转为十进制

注意 用了lombok注解@EqualsAndHashCode(@Data里面也包含)时,重写了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,都会继承自某个类。下图是PersonStudent的继承树:

┌───────────┐
│  Object   │
└───────────┘
      ▲
      │
┌───────────┐
│  Person   │
└───────────┘
      ▲
      │
┌───────────┐
│  Student  │
└───────────┘

Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。

类似的,如果我们定义一个继承自PersonTeacher,它们的继承树关系如下:

       ┌───────────┐
       │  Object   │
       └───────────┘
             ▲
             │
       ┌───────────┐
       │  Person   │
       └───────────┘
          ▲     ▲
          │     │
          │     │
┌───────────┐ ┌───────────┐
│  Student  │ │  Teacher  │
└───────────┘ └───────────┘

protected

继承有个特点,就是子类无法访问父类的private字段或者private方法。例如,Student类就无法访问Person类的nameage字段:

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出现在Shapepermits列表中。但是,如果定义一个Ellipse就会报错:

public final class Ellipse extends Shape {...}
// Compile error: class is not allowed to extend sealed class: Shape

原因是Ellipse并未出现在Shapepermits列表中。这种sealed类主要用于一些框架,防止继承被滥用。

sealed类在Java 15中目前是预览状态,要启用它,必须使用参数--enable-preview--source 15

向上转型

如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:

Student s = new Student();