Java基础学习:继承、Object通用方法、关键字,看了不会算我输

这段时间把基础这部分又过了一遍,归纳整理了有关继承、Object通用方法、关键字的相关知识。

继承

访问权限

Java中有四种访问权限, 其中三种有访问权限修饰符,分别为private、public、protected,还有一种不带任何修饰符(default)。如果不加访问修饰符,表示包级可见。

小编准备了一套Java编程学习资料,加群:985331340免费领取,你敢来我就敢送!

protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。

设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。

如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则

也就是说只要我是你的子类,继承了你的方法,那就只能比你的访问级别更高。长江后浪推前浪,只有比父亲级别高,才能保证别人只要跟你的父亲合作时就能使用你的资源。

字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。

public class AccessExample {
    public String id;
}

可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。

public class AccessExample {

    private int id;

    public String getId() {
        return id + "";
    }

    public void setId(String id) {
        this.id = Integer.valueOf(id);
    }
}

但是也有例外,如果是包级私有的类或者私有的嵌套类,那么直接暴露成员不会有特别大的影响。

public class AccessWithInnerClassExample {

    private class InnerClass {
        int x;
    }

    private InnerClass innerClass;

    public AccessWithInnerClassExample() {
        innerClass = new InnerClass();
    }

    public int getValue() {
        return innerClass.x;  // 直接访问
    }
}

所以总结如下:

private: Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象 访问,其子类不能访问,更不能允许跨包访问。
default:即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本 
身的方法及子类访问,即使子类在不同的包中也可以访问。
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且 允许跨包访问。

抽象类与接口

抽象类与接口是java语言中对抽象概念进行定义的两种机制,两者有一定的区别也有很多的相似性。很多常见的面试题都会出诸如抽象类和接口有什么区别,什么情况下会使用抽象类和什么情况你会使用接口

抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。

抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    // abstract method
    abstract void service(ServletRequest req, ServletResponse res);

    void init() {
        // Its implementation
    }
    // other method related to Servlet
}
public class HttpServlet extends GenericServlet {
    void service(ServletRequest req, ServletResponse res) {
        // implementation
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        // Implementation
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        // Implementation
    }

    // some other methods related to HttpServlet
}

接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情。

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

接口的字段默认都是 static 和 final 的。

当你实现这个接口时,你就需要实现接口的方法。

比较:

  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
  • 抽象类可以有构造器,接口不能有构造器。
  • 抽象类的实现:子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。
  • 接口的实现:子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现

使用选择:

接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
  • 需要使用多重继承。

抽象类:

  • 需要在几个相关的类中共享代码。 - 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

重写与重载

  1. 重写(Override)

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。

为了满足里式替换原则,重写有有以下两个限制:

  • 子类方法的访问权限必须大于等于父类方法;
  • 子类方法的返回类型必须是父类方法返回类型或为其子类型。 使用 @Override注解,可以让编译器帮忙检查是否满足上面的两个限制条件。
  • 重载(Overload)

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。

Object 通用方法

Java类层次结构的顶层是Object类,所有的其他类都隐式的继承于它。
public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native Class<?> getClass()

protected void finalize() throws Throwable {}

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException

重点方法:equals、hashCode、toString和clone

equals()

equals()方法实现需要满足以下几个规则限制:

  • 自反性:对象x必须与其自身相等,equals(x)返回true -
  • 对称性:如果equals(y)为true,则y.equals(x)也要返回true -
  • 传递性:如果equals(y)为true,并且y.equals(z)也为true,则x.equals(z)也要为true
  • 一致性:多次调用equals()方法应该返回相同值,除非对用于判等的任何一个属性进行了修改
  • 与null判等:equals(null)总是要返回false
x.equals(x); // true     -------自反性

x.equals(y) == y.equals(x); // true      -------对称性

if (x.equals(y) && y.equals(z))         -------传递性
    x.equals(z); // true;

x.equals(y) == x.equals(y); // true         -------一致性

x.equals(null); // false;           -------与null判等

等价与相等

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y);      // false

hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

为了避免得到不可预期的结果,尽可能在实现equals()和hashCode()方法时使用final字段,从而保证方法的结果不会受到字段变化的影响(尽管真实场景中未必发生)。

最后,要确保在实现equals()和hashCode()方法是使用相同的字段,以确保在不可预期的字段调整时保证这两个方法行为的一致性。

toString()

默认情况下,toString()的结果仅仅返回以@符分隔的全类名与对象哈希值串。(ToStringExample@4554617c) 其中 @ 后面的数值为散列码的无符号十六进制表示。

clone()

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

clone()方法的目的很简单——返回对象实例的拷贝

注意:clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛 CloneNotSupportedException。

在重载时需要声明为public并把返回值类型调整为重载类自身类型。再次,重载类需要实现Cloneable接口(尽管该接口作为一种声明,并未提供任何方法定义),否则将会抛出CloneNotSupportedException异常。最后,在实现clone()方法时要先调用super.clone()然后再执行其他需要的动作。

关键字

final

1.声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

  • 对于基本类型,final 使数值不变;
  • 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;
// x = 2;  // cannot assign value to final variable 'x'
final A y = new A();
y.a = 1;

2.声明方法不能被子类重写。

private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

3.声明类不允许被继承。

static

static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,被static修饰的成员变量和成员方法独立于该类的任何对象。

1. 静态变量

  • 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
  • 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A {

    private int x;         // 实例变量
    private static int y;  // 静态变量

    public static void main(String[] args) {
        // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
        A a = new A();
        int x = a.x;
        int y = A.y;
    }
}
  1. 静态方法 静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法
public class A {

    private static int x;
    private int y;

    public static void func1(){
        int a = x;
        // int b = y;  // Non-static field 'y' cannot be referenced from a static context
        // int b = this.y;     // 'A.this' cannot be referenced from a static context
    }
}

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。

  1. 静态语句块

静态语句块在类初始化时运行一次。

public class A {
    static {
        System.out.println("123");      //123
    }

    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new A();
    }
}
  1. static和final一块用表示什么
  2. static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
  3. 对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
  4. 对于方法,表示不可覆盖,并且可以通过类名直接访问
  5. 初始化顺序 静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。

存在继承的情况下,初始化顺序为:

  • 父类(静态变量、静态语句块)
  • 子类(静态变量、静态语句块)
  • 父类(实例变量、普通语句块)
  • 父类(构造函数)
  • 子类(实例变量、普通语句块) - 子类(构造函数)

结语

写了这么多,收获也很多。全当一个归纳总结了。看了这么多例子,最重要的是要举一反三,先问是不是,再问为什么。多用,多学

猜你喜欢

转载自blog.csdn.net/qq_43202482/article/details/89074252