Notes
Search…
Basic

访问修饰符

Java 访问修饰符有四个,权限由小到大依次为:
  • private:可以修饰数据成员、构造方法、方法成员,不可修饰类(外部类或顶层类,不包括内部类)。被 private 修饰后,只能在定义它们的类中访问。
  • default:类、数据成员、构造方法、方法成员都能使用,即不写修饰符。默认权限即同包权限,只能在定义它们的类中以及同包的类中使用。
  • protected:可以修饰数据成员、构造方法、方法成员,不可修饰类(外部类或顶层类,不包括内部类)。可以被定义它们的类、同包类、子类访问。
  • public:类、数据成员、构造方法、方法成员都能使用。可以在任何地方访问。

变量

类变量

  1. 1.
    有static修饰。
  2. 2.
    存储在方法区。

成员变量

  1. 1.
    存储在堆中。
  2. 2.
    修饰符有:public、protected、private、final、volatile、transient。

局部变量

  1. 1.
    存储在栈中。
  2. 2.
    在方法体、代码块、形参中。
  3. 3.
    修饰符有:final。

方法

重写

哪些方法不能被重写

  • final方法。
  • static方法。
  • private方法。
  • 构造方法。

内部类

  • 顶层类(Top-level):类的定义代码不嵌套在其它类中。
  • 内部类(Inner Class):
    • 静态内部类(Static Inner Class):
      • 不依赖外部类实例而被实例化。
      • 不能访问外部类的普通成员,只能访问外部类的静态成员和静态方法(包括私有)。
    • 成员内部类(Member Inner Class):
      • 可以自由引用外部类的属性和方法,无论是静态还是非静态。
    • 局部内部类(Local Inner Class):
      • 定义在一个代码块中的类,作用范围为所在代码块。
      • 与局部变量一致,不能被public、protected、private、static 修饰。
      • 只能访问final 类型的局部变量。
    • 匿名内部类(Anonymous Inner Class):
      • 没有构造方法。
      • 必须继承(extend)其它类或接口。
  • 非静态内部类只能在外部类实例化后,才能被实例化;不能有静态成员
  • 静态内部类、成员内部类可以看做是类的静态成员和非静态成员,支持的访问修饰符一致。
  • 局部内部类可以看做局部变量,支持的访问修饰符一致。
  • 外部类可以看做是顶层类,所以与文件名一致的顶层类可以被 public 修饰,而其它则不行。
/**
* 外部类
* 可以被 public、abstract、final 修饰
* 不能被 protect、private、static 修饰
*/
public class Outter {
// 静态内部类
// 可以被 public、protected、private、final、abstract 修饰
static class StaticInner{}
// 成员内部类
// 可以被 public、protected、private、final、abstract 修饰
class MemberInner{}
public void f() {
// 局部内部类
// 不能被 public、protected、private、static、abstract 修饰
// 可以被 final 修饰
class LocalInner{}
}
public static void g() {
// 局部静态内部类
// 不能被 public、protected、private、static、abstract 修饰
// 可以被 final 修饰
class LocalInner{}
}
public void p() {
// 匿名内部类
new Runnable() {
@Override
public void run() {
System.out.println(1);
}
};
}
}
// 顶层类
// 不能被 public、protected、private、static 修饰
// 可以被 final、abstract 其中之一修饰
class TopLevel{}

继承

  • 子类可以继承父类的非私有方法和属性。
  • Java 只能单一继承。

抽象类

  • 如果一个类中有一个方法被 abstract 修饰,那么这个类是抽象类,必须被 abstract 修饰。
  • abstract 只能修饰类的方法,不能修饰变量。
  • 被 abstract 修饰的方法,不能被 private、static、synchronized、native 修饰。
  • 被 abstract 修饰的方法,实现后必须是相同或更低的访问级别。
抽象类的特点是:
  1. 1.
    不允许被实例化,只能被继承。
  2. 2.
    可以包含属性和方法,方法可以实现也可以不实现。
  3. 3.
    非抽象子类继承抽象类,比如实现抽象方法。
抽象类可以解决的问题:代码复用问题。
我们也可以继承非 final 的 public 类的一个空的方法,为什么还需要抽象类的抽象方法? 1. 抽象类从编译层面强制要求实现抽象方法,减少程序员犯错。 2. 一个空的方法引起误会,影响可阅读性。 3. public 类可以被实例化,调用空方法无意义。

接口

  • 接口只能被 public 和 abstract 修饰,或者没有修饰符。当没有修饰符,只能被同包使用。接口默认被 abstract 修饰的,所以添不添加 abstract 修饰符都一样。
  • 接口方法默认被 public、abstract 修饰,所以有没有上述两个修饰符都一样。
  • 当接口方法被 default(JDK1.8增加)修饰时,表示有默认实现,需要用{}来实现 body。
  • 当接口方法被 static(JDK1.8增加)修饰时,只能用接口类(不包括子类)访问方法,不能用接口实例访问。
  • 接口中的成员变量默认是 public、static、final 的,所以有没有这三个修饰符都一样。必须赋初值。
接口的特点是:
  1. 1.
    不能包含成员变量。
  2. 2.
    只能声明方法,不能实现。(default 例外)。
  3. 3.
    非抽象类实现接口时,必须实现接口所有方法。
为什么在 Java8 中接口可以定义 default 方法? Java8 之前,接口不能有默认实现,所以给接口增加一个方法,所有的子类都必须实现,default 可以解决这个问题。
接口解决的问题:侧重于解耦,对行为的抽象,相当于一组协议或契约,约定与实现分离。

抽象类与接口

  • 接口不能有私有变量和方法,而抽象类可以。
  • 接口可以看做是一种特殊的抽象类。
  • 一个类可以实现多个接口,但是只能继承一个抽象类。
  • 接口表示 has-a 的关系,而抽象类表示 is-a 的关系。
  • 接口可以继承接口,抽象类可以实现接口,抽象类可以继承实体类。

接口 OR 抽象类?

  1. 1.
    抽象类:表示一种 is-a 的关系,并且是为了解决代码复用的问题;
  2. 2.
    接口:表示一种 has-a 关系,并且是为了解决抽象而非代码复用的问题。
设计思路:
  • 抽象类:自下而上,先有子类代码复用,再抽象成上层的父类。
  • 接口:自上而下,先设计接口,再考虑具体实现。

Object 的所有方法

public class Object {
private static native void registerNatives();
static {
registerNatives();
}
// 经常被覆盖的方法
public boolean equals(Object obj) {
return (this == obj);
}
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
// native 方法
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
// final 方法
public final native Class<?> getClass();
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 {
wait(0);
}
// 不建议使用的方法
protected void finalize() throws Throwable { }
}

getClass()

final 方法不允许重写,返回运行时的 Class 对象。

hashCode()

默认实现是将对象的内部地址转换成一个整数。可以被覆盖,覆盖的约定如下:
  • 若对象没有被改变,则无论调用多少次 hashCode(),都会返回相同的整数值。
  • 没有必要在不同的程序中保持相同的值。
  • equals()返回 true,则 hashCode()必须相等。
  • equals()返回 falsehashCode()可以相同;但是尽量不相同,可以提高 hash 表的性能。

equals()

默认实现是比较两个对象的地址是否相等。可以被覆盖,约定如下:
  • 自反性,x.equals(x) == true
  • 对称性,若x.equals(y) == true,则y.equals(x) == true
  • 传递性,若x.equals(y) == true && y.equals(z) == true,则x.equals(z) == true
  • x.equals(null) == false

clone()

由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone()方法会抛出异常。
返回对象的浅拷贝

wait()wait(long)wait(long, int)

  • 让当前线程等待。
  • 当前线程必须是调用wait()方法对象的监视器所有者,否则会发生IllegalMonitorStateException异常。
  • wait()方法会将当前线程放置在对象的等待集中,并让当前线程放弃该对象监视器的所有权,即放弃了锁
  • 会被以下事件唤醒:
    • 其它线程调用此对象的notify()方法,并恰巧此线程被选中。
    • 其它线程调用此对象的notifyAll()方法。
    • 其它线程调用 Thread.interrupt()方法中断此线程。
    • 若设置了超时时间,时间超过后。
  • 被唤醒后,线程在等待集中被移除,以常规方式与其它线程竞争,获取该对象的监视器所有权,一旦获得,则 wait()方法返回。

注意

在没有被通知、中断或超时的情况下,线程还可以唤醒一个所谓的虚假唤醒 (spurious wakeup)。虽然这种情况在实践中很少发生,但是应用程序必须通过以下方式防止其发生。
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
若要成为某个对象监视器的所有者,可以有以下几种方式:
  • 执行对象的同步实例方法。
  • 使用 synchronized内置锁。
  • 对于 Class 对象,执行该类的同步静态方法。
一个线程只能有一个对象的监视器。所以 wait、notify 方法只能在同步代码块或同步方法中使用
wait()释放了锁,而 sleep()仍然持有锁。

notify()notifyAll()

  • 唤醒一个或全部在此对象监视器上等待的线程。
  • 若是一个,则是随机选取一个。
  • 直到当前线程放弃了对象上的锁后,被唤醒的线程才会继续竞争锁。即 notify 方法不会立即释放锁
  • 当前线程必须是该对象监视器的所有者,不然会抛出 IllegalMonitorStateException。
wait 和 notify 一般配套使用,见下面的例子:
public class ThreadTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
synchronized (r) {
try {
System.out.println("main thread 等待t线程执行完");
r.wait();
System.out.println("被notity唤醒,得以继续执行");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("main thread 本想等待,但被意外打断了");
}
System.out.println("线程t执行相加结果" + r.getTotal());
}
}
}
class MyRunnable implements Runnable {
private int total;
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
System.out.println("Thread name is:" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
total += i;
}
notify();
System.out.println("执行notif后同步代码块中依然可以继续执行直至完毕");
}
System.out.println("执行notif后且同步代码块外的代码执行时机取决于线程调度");
}
public int getTotal() {
return total;
}
}
main thread 等待t线程执行完
Thread name is:Thread-0
执行notif后同步代码块中依然可以继续执行直至完毕
执行notif后且同步代码块外的代码执行时机取决于线程调度
被notity唤醒,得以继续执行
线程t执行相加结果45
Copy link
On this page
访问修饰符
变量
类变量
成员变量
局部变量
方法
重写
内部类
继承
抽象类
接口
抽象类与接口
接口 OR 抽象类?
Object 的所有方法
getClass()
hashCode()
equals()
clone()
wait()、wait(long)、wait(long, int)
notify()、notifyAll()