Java面向对象(下)

本文由码农网 –
单劼原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

Java—面向对象

我们都曾在POJO中重写过equals(),compareTo()和toString()方法。但是另有其他能做到职责分离的更好的方法并带来更简洁的代码。阅读这篇文章来一探究竟吧!

1.对象转型  

  1. 一个基类的引用类型变量可以“指向”其子类的对象
  2. 一个基类的引用不可以访问其子类对象新增的成员。
  3. 可以使用引用变量instanceof类名来判断该引用型变量所指向的对象是否属于该类或该类的子类
  4. 子类的对象可以当作基类的对象来使用称作向上转型(upcasting),反之称为向下转型(downcasting)
  5. 澳门新葡亰3522平台游戏,一个子类的对象可以向上造型为父类的类型。即定义父类型的引用可以指向子类的对象

  先看一个例子:

public class Person {    String name;    char gender;    Person(String name,char gender){        this.name = name;        this.gender = gender;    }}  public class Student extends Person {    double score;    Student(String name,char gender,double score){        super(name,gender);  //调用父类有参构造        this.score = score;        super.name = "Tom";    }         public static void main(String[] args) {        Person p = new Student("Tom",'男',80);    //向上转型        p.score = 100;        //编译错误,Java编译器会根据引用的类型,而不是对象的类型来检查调用的方法是否匹配。
                             //向上转型即基类引用指向子类,基类引用可以指向子类的对象,但通过父类的引用只能访问父类所定义的成员,不能访问子类扩展的部分
    System.out.println(p instanceof Person);  //true
    System.out.println(p instanceof Student);  //true

    }}

更简明的职责——摆脱equals、compareTo和toString方法

你曾经查看过java文档中的Object类吗?也许吧。每当你向上追溯继承树的时候都会止步于这个类。你会注意到,该类有几个方法是每一个类都必须继承的。而你最喜欢重写的方法可能就是toString().equals() and .hashCode() 这三个了。(至于为何总是应该同时重写后两个方法,请看Per-Åke
Minborg写的这篇文章:

但是仅仅有这几个方法显然是不够的。许多人将标准库中的其他的接口如Comparable和Serializable加以组合。但是这样真的明智吗?为什么每个人都很迫切地去自己实现这些方法呢?事实上,当你准备将对象存储在一些容器中,如HashMap,并且想要控制哈希冲突的时候,实现你自己的.equals()方法和.hashCode()方法确实有它的意义,但实现compareTo()和toString()方法又是为何呢?

本篇文章中我将提出一种使用到Speedment
开源项目上的软件设计方法,这里的对象的方法被定义为存储于变量上的方法引用,而不是重写它们。这样做确有一些好处:你的POJO将会更短小简洁,通用的方法可以不需要继承而进行复用并且你可以因地制宜地使用它们。

2.Object类 

  Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入;

  Object类是所有Java类的祖先。每个类都使用 Object
作为超类。所有对象都实现这个类的方法。可以使用类型为Object的变量指向任意类型的对象

Object类是所有java类的根基类

如果在类的声明中未使用extends关键字致命其基类,则默认基类为Object类,也就是说

public class Person {…}等价于public classPerson extends Object {…}

  Object类提供的方法: 

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • Class< > getClass()
  • int hashCode()
  • void notify()
  • void notifyAll()
  • String toString()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

  java的任何类都继承了这些函数,并且可以覆盖不被final修饰的函数。例如,没有final修饰的toString()函数可以被覆盖,但是final wait()函数就不行。主要用的方法有toString和equals方法

原始的代码

首先我们来看下面的代码:这里有一个典型的Java类Person。在使用中需要从一个Set中打印出每一个person对象,并且按照姓在前和名在后的顺序排列(以防出现两个相同姓氏的人)。

Person.java

public class Person implements Comparable<Person> {
    private final String firstname;
    private final String lastname;
    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname  = lastname;
    }
    public String getFirstname() {
        return firstname;
    }
    public String getLastname() {
        return lastname;
    }
    @Override
    public int hashCode() {
        int hash = 7;
        hash = 83 * hash + Objects.hashCode(this.firstname);
        hash = 83 * hash + Objects.hashCode(this.lastname);
        return hash;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final Person other = (Person) obj;
        if (!Objects.equals(this.firstname, other.firstname)) {
            return false;
        }
        return Objects.equals(this.lastname, other.lastname);
    }
    @Override
    public int compareTo(Person that) {
        if (this == that) return 0;
        else if (that == null) return 1;
        int comparison = this.firstname.compareTo(that.firstname);
        if (comparison != 0) return comparison;
        comparison = this.lastname.compareTo(that.lastname);
        return comparison;
    }
    @Override
    public String toString() {
        return firstname + " " + lastname;
    }
}

Main.java

public class Main {
    public static void main(String... args) {
        final Set
      people = new HashSet<>();
        people.add(new Person("Adam", "Johnsson"));
        people.add(new Person("Adam", "Samuelsson"));
        people.add(new Person("Ben", "Carlsson"));
        people.add(new Person("Ben", "Carlsson"));
        people.add(new Person("Cecilia", "Adams"));
        people.stream()
            .sorted()
            .forEachOrdered(System.out::println);
    }
}

Output

run:
Adam Johnsson
Adam Samuelsson
Ben Carlsson
Cecilia Adams
BUILD SUCCESSFUL (total time: 0 seconds)

Person 类实现了一些方法来控制输出。 hashCode()equals() 方法确保同一个person对象不会被重复添加到set中。.compareTo() 方法用于排序方法中生成应有的顺序。而重写方法toString()是在System.out.println() 被调用的时候控制每个Person对象的输出格式。你认出这种结构了吗?几乎任何一个java工程中都会有它。

  1.toString方法    

  • Object类中定义有public String
    toString()方法,其返回值是String类型,描述当前对象的有关信息
  • 在进行String与其他类型数据的链接操作时(如:System.out.print(“info”+person)),将自动调用该对象类的toString()方法
  • 可以根据需要在用户自定义类型中重写toString()方法

    //测试类public class Test {    public static void main(String[] args) {        Dog d = new Dog();        System.out.println("d: "+d);        //等价于         System.out.println("d: "+d.toString;    }}class Dog {    }//输出结果://d: test.Dog@2a139a55//d: test.Dog@2a139a55
    

    输出的结果就是类的对象的相关信息  

   如果对于Object中某些方法不满意可以自己在自己类中进行重写,例如:

public class Test {    public static void main(String[] args) {        Dog d = new Dog();        System.out.println("d: "+d);        //等价于        System.out.println("d: "+d.toString;    }}class Dog {        public String toString(){        return "Dog";    }}//输出结果d: Dogd: Dog

替代这些代码

相比于将所有这些方法写入Person类中,我们可以让它保持尽量的简洁,使用方法引用去处理它们。我们可以删除所有equals(),hashCode(),compareTo()和toString()的样板式代码,取而代之的是下面介绍的两个静态变量:COMPARATOR 和TO_STRING

Person.java

public class Person {
    private final String firstname;
    private final String lastname;
    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname  = lastname;
    }
    public String getFirstname() {
        return firstname;
    }
    public String getLastname() {
        return lastname;
    }
    public final static Comparator<Person> COMPARATOR =
        Comparator.comparing(Person::getFirstname)
            .thenComparing(Person::getLastname);
    public final static Function<Person, String> TO_STRING =
        p -> p.getFirstname() + " " + p.getLastname();
}

Main.java

public class Main {
    public static void main(String... args) {
        final Set
      people = new TreeSet<>(Person.COMPARATOR);
        people.add(new Person("Adam", "Johnsson"));
        people.add(new Person("Adam", "Samuelsson"));
        people.add(new Person("Ben", "Carlsson"));
        people.add(new Person("Ben", "Carlsson"));
        people.add(new Person("Cecilia", "Adams"));
        people.stream()
            .map(Person.TO_STRING)
            .forEachOrdered(System.out::println);
    }
}

Output

run:
Adam Johnsson
Adam Samuelsson
Ben Carlsson
Cecilia Adams
BUILD SUCCESSFUL (total time: 0 seconds)

这样实现的好处是我们可以在不用更改Person类的情况下替换排序策略或打印格式。这将使代码拥有更强的可维护性和复用性,更不用说更快的编写速度了。

  2.equals方法

    Object类中定义了public boolean
equals(Object obj)方法

    提供定义对象是否相等的逻辑(比较的是在堆内存中的内存地址),使用格式:

x.equals当x和y是同一个对象的应用时返回true,斗则返回false

    注意:jdk提供的一些类,如String,Date等,重写了Object的equals方法,调用这些类的equals方法,x.equals,当x和y所引用的对象是同一类对象且属性值相等时(并不一定是相同对象),返回true,否则返回false
(重写了Object的equals方法的封装,比如String类对象,他们使用equals方法比较的就是属性值即成员变量的值)

    可以根据需要在用户自定义类型中重写equals方法

public class Test {    public static void main(String[] args) {        Dog d1 = new Dog();        Dog d2 = new Dog();                System.out.println(d1 == d2);        System.out.println(d1.equals;            }}class Dog {    }//输出结果://false//false

public class Test {    public static void main(String[] args) {        Dog d1 = new Dog();        Dog d2 = new Dog();                System.out.println(d1 == d2);        System.out.println(d1.equals;            }}class Dog {        public boolean equals(Object obj) {        return true;    }}//输出结果://false//true

    从上面两个例子可以看出系统方法也是可以重写的,同时可以看出“==”这个运算符,如果是引用类型比较的就是堆内存中的内存地址,如果是基本数据类型比较的是值(基本数据类型的局部变量存放在栈中),创建出来的对象的成员变量存放在堆内存的常量池中  

  3.hashCode方法   

    public``native``int``hashCode();

    hash值:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

    情景:考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)。

    大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值。实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值,
就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。

    重写hashCode()方法的基本规则:

    1. 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
    2. 当两个对象通过equals()方法比较返回true时,则两个对象的hashCode()方法返回相等的值。
    3. 对象用作equals()方法比较标准的Field,都应该用来计算hashCode值。

Object本地实现的hashCode()方法计算的值是底层代码的实现,采用多种计算参数,返回的并不一定是对象的内存地址,具体取决于运行时库和JVM的具体实现。

public boolean equals(Object obj) {       return (this == obj);}