类和对象
类:对象行为和状态的抽象描述
对象:类的一个实例
JVM内存结构
堆
存放对象实例;几乎所有的对象实例都在堆区中分配内存
虚拟机栈
存储局部变量等
方法区
存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等
变量
变量分为:
- 成员变量:类中方法外,包括实例变量(non-static)和静态变量(static)
- 局部变量:类中方法/代码块中
实例变量 (instance variable) & 局部变量 (local variable)
实例变量 | 局部变量 | |
---|---|---|
声明位置 | 类中方法外,没有static修饰 | 类中方法或代码块中 (形参,方法体局部变量,代码块局部变量) |
内存储存位置 | 堆区 | 栈区 |
修饰符 | 权限修饰符等 | final |
初始化 | 有默认值 | 没有默认值,声明后必须初始化才能使用 |
生命周期 | 对象创建时创建,对象销毁时销毁 | 方法调用时创建,方法运行结束销毁 |
方法的参数传递机制 (值传递)
基本数据类型:实参给形参传递数据值
引用数据类型:实参给形参传递地址值,两者指向同一个对象
例子:
字符串对象是不可变的
public static void main(String[] args) {
String str = "hello";
changeString(str);
System.out.println(str); //输出:hello
}
public static void changeString(String s) {
s = "hi";
}
重载 (Overload)
同一个类中,方法名相同,形参列表不同(类型,数量,顺序);与返回值类型、权限修饰符、形参变量名、方法体无关
可变个数形参
等价于数组类型形参
public void f() {}
public void f(String args) {}
public void f(String ... args) {} //不能和下面的方法同时存在
//public void f(String[] args) {}
- 调用时可以不传参数或传入多个参数
- 可变个数形参必须在参数列表的末尾,且最多只能声明一个可变形参
- 和其他同名不同形参列表的方法构成重载
- 优先调用形参个数不可变的方法
构造器
- 所有的类都有构造器
- 如果没有显式声明构造器,编译器会自动添加一个默认的无参构造器(其权限修饰符与类的一致)
- 如果显式声明了构造器,编译器不会自动添加默认的无参构造器,按需手动添加
- 构造器名称与类名一致,且没有返回值类型
封装 (Encapsulation)
权限修饰符 | 本类 | 本包 | 其他包的子类 | 任意位置 | 使用对象 |
---|---|---|---|---|---|
private | Y | N | N | N | 变量、方法、内部类 |
default | Y | Y | N | N | 外部类、接口、变量、方法、内部类 |
protected | Y | Y | Y/N | N | 变量、方法、内部类 |
public | Y | Y | Y | Y | 外部类、接口、变量、方法、内部类 |
protected:
- 子类和父类在同一包中:protected成员能被本包中任何其他类访问
- 子类和父类不在同一包中:在子类中,子类实例可以访问其从父类继承的protected方法,父类实例不能访问其protected方法
继承 (Inheritance)
- 子类继承父类的属性和方法;子类可以通过public修饰的set/get方法来访问父类的私有成员
- 子类不继承父类的构造器
- 子类在构造器中调用父类的构造器:
若父类有无参构造器:编译器在子类的构造器中自动调用父类的无参构造器
若父类没有无参构造器:必须在子类的构造器中显式地通过super(实参列表)调用父类的有参构造器 - 单继承,且多重继承
重写 (Override)
子类根据需要重新实现自父类继承来的方法
- 方法名不变,参数列表不变
- 权限修饰符不能低于父类方法的权限修饰符 (public>protected>default>private) (private方法不能被重写)
- 返回值类型可以不同,但必须是父类方法返回值类型的子类
多态 (Polymorphism)
多态是同一个行为具有多个不同表现形式或形态的能力
- 必要条件:
(1)继承
(2)重写
(3)父类引用指向子类对象Parent p = new Child();
- 使用多态方式调用方法时:编译看左边父类,运行看右边子类
编译时,检查父类是否有该方法,如果没有,则编译错误;如果有,执行子类重写后的方法 - 向上转型:子类对象赋值给父类变量(自动类型转换)
向下转型:父类对象赋值给子类变量(强制类型转换),可能导致ClassCastException - instanceof: 返回true/false
if (对象 instanceof 类) {}
注意:多态性只适用于方法,不适用于属性
this
表示当前对象或正在创建的对象
- this.属性
区别同名的实例变量和局部变量 - this.方法
调用当前对象的成员方法,可省略“this.” - this()/this(实参列表)
只能出现在构造器的首行
super
表示父类的
- super.属性
区别同名的子类实例变量和父类实例变量 - super.方法
子类重写了父类的方法,仍需调用父类中被重写的方法 - super()/super(实参列表)
只能出现在子类构造器的首行
非静态代码块
[修饰符] class 类名 {
{
非静态代码块
}
}
- 每次创建对象都会执行
- 优先于构造器执行
实例初始化
- 实例对象创建时执行实例初始化方法
- 实例初始化方法:<init>()/<init>(形参列表),是由编译器生成
- 实例初始化方法包含三部分:
(1)实例变量的显式赋值
(2)非静态代码块
(3)构造器
(1)和(2)按顺序执行,(3)最后执行 - 一个类有几个构造器,就有几个实例初始化方法
- super()/super(形参列表):调用父类的构造器,实际是调用父类的实例初始化方法;只能出现在子类构造器的首行,实际是只能出现在子类实例初始化方法的首行
- 先执行父类实例初始化方法,再执行子类实例初始化方法
- 如果子类重写了父类的方法,在创建子类对象时,若该方法在父类实例初始化方法中被调用,执行重写后的方法
Object类
Object类是所有类的父类,只有一个空参的构造器
子类可以使用Object的所有方法
- public String toString()
返回“对象运行时的类@对象的hash值的十六进制形式”;通常被重写 - public final Class<?> getClass()
返回对象运行时的类 - protected void finalize()
实例被GC回收时运行 - public int hashCode()
返回对象的hash值,表示在哈希表中的位置 - public boolean equals(Object obj)
比较当前对象this和指定对象obj的内存地址是否相等(等价于“==”)
如果重写该方法,则必须重写hashCode方法;且重写必须遵循:
非空性:对于任意非空引用x,x.equals(null) = false
自反性: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,y引用的对象没有发生变化,那么重复调用x.equals(y)的结果一致
final
- 修饰类:不能被继承
- 修饰方法:可以被继承,不能被重写
- 修饰变量:必须显式指定初始值,且不能被修改
static
静态方法
- 本类中直接使用,其他类中“类名.方法名”调用
- 静态方法中不能使用this,super,类的非静态成员(不能直接使用,要先实例化)
静态变量
- 静态变量被所有类实例对象共享,仅在类初次加载时初始化一次
- 储存在方法区
注意:
- 静态变量和静态方法都可以被继承和隐藏而不能被重写,因此不能实现多态
- 如果子类重新定义了父类的静态变量和静态方法,则在子类中调用的是子类的静态变量和静态方法;反之,在子类中调用的是父类的静态变量和静态方法
静态代码块
[修饰符] class 类名 {
static {
静态代码块
}
}
- 类初始化时执行
- 不能使用this,super,非静态成员
类初始化
- 类初始化方法:<clinit>(),一个类只有一个,且只执行一次
- 类初始化方法包括:
(1)静态变量的显式赋值
(2)静态代码块
(1)和(2)按顺序执行 - 先初始化父类,再初始化子类
- 先类初始化,再实例初始化
单例模式
一个类只有一个对象实例
- 私有的构造器
- 类的单个静态实例
- 供外界访问该实例的静态方法
饿汉式实现(eager initialization)
类加载时就实例化,线程安全
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
懒汉式实现(lazy initialization)
线程不安全版:
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
线程安全版:
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
abstract
抽象类:
[权限修饰符] abstract class 类名 {
}
抽象方法:
[其他修饰符] abstract 返回值类型 方法名(形参列表); //抽象方法没有方法体
- 抽象类不能直接实例化
- 抽象类有构造器,供子类实例化中调用
- 子类继承抽象类,必须重写所有的抽象方法,否则该子类必须定义为抽象类
- 抽象类引用指向其子类对象构成多态
- 抽象类可以没有抽象方法;包含抽象方法的类必须声明为抽象类
- 抽象类不能被final修饰;抽象方法不能被private,final,static修饰
模板方法模式
public abstract class CalTime {
public long getTime() {
long start = System.currentTimeMillis();
runTask();
long end = System.currentTimeMillis();
return end - start;
}
abstract void runTask(); //抽象出不确定的部分,由子类具体实现
}
public class MyCalTime extends CalTime {
void runTask() {
//方法体
}
}
接口
[权限修饰符] interface 接口 {
}
[修饰符] class 实现类 implements 接口 {
}
接口的特性
- 接口不能实例化,只能被类实现,且一个类可以实现多个接口
- 类实现接口时,必须重写所有抽象方法,否则该类必须为抽象类
- 接口引用指向其实现类对象构成多态
- 接口没有构造方法
- 接口支持多继承
接口的成员
Java 8之前:
- 变量类型只能是public static final,修饰符均可省略
- 方法类型只能是public abstract,修饰符均可省略
Java 8之后:
- 可以有包含具体实现的静态方法,类型为public static,其中public可省略
- 可以有包含具体实现的默认方法,类型为public default,其中public可省略
常用接口
- java.lang.Comparable
抽象方法:int compareTo(T o) - java.util.Comparator
抽象方法:int compare(T o1, T o2)
代理模式
使用代理对象来控制对真实对象的访问,应用场景:
- 安全代理:屏蔽对真实对象的直接访问
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级代理类对象,需要时再加载真实对象
代理模式有静态代理和动态代理两种实现方式,这里只讨论静态代理:
- 定义接口及其实现类
- 创建代理类并实现该接口
- 将目标对象注入代理类,在代理类的实现方法中调用目标类的对应方法
//接口
public interface Network {
void connect();
}
//目标类
public class Server implements Network {
public void connect() {
System.out.println("connect network");
}
}
//代理类
public class ProxyServer implements Network {
private Server server;
public ProxyServer(Server server) {
this.server = server;
}
public void connect() {
System.out.println("before connect");
server.connect();
System.out.println("after connect");
}
}
//实际使用
public class Main {
public static void main(String[] args) {
ProxyServer proxyServer = new ProxyServer(new Server());
proxyServer.connect();
}
}
内部类
成员内部类和局部内部类编译后都会生成相应的字节码文件(.class)
成员内部类
- 作为外部类的成员:
(1)可以访问外部类的其他成员
(2)可以用static修饰
(3)可以用4种权限修饰符修饰 - 作为一个类:
(1)类中可以定义属性,方法,构造器等
(2)可以用final修饰
(3)可以用abstract修饰
静态内部类
class OutClass {
static class InClass {
static void f1() {}
void f2() {}
}
}
- 可以有静态变量和静态方法/代码块
- 不能直接访问外部类的非静态成员
- 在外部类的外面访问静态内部类的成员:
(1)静态成员:
OutClass.InClass.f1();
(2)非静态成员:
//(1)创建静态内部类对象
OutClass.InClass inner = new OutClass.InClass();
//(2)通过对象调用非静态成员
inner.f2();
非静态内部类
class OutClass {
public InClass getInClass() {
return new InClass();
}
class InClass {
void f() {}
}
}
- 不能有静态成员
- 外部类的静态成员不能直接访问非静态内部类
- 在外部类的外面访问非静态内部类的成员:
//(1)创建外部类对象
OutClass outer = new OutClass();
//(2)通过外部类对象创建或获取非静态内部类对象
//创建
OutClass.InClass inner_1 = outer.new InClass();
//获取
OutClass.InClass inner_2 = outer.getInClass();
//(3)通过对象调用成员
inner.f();
局部内部类
class OutClass {
void f() {
class InClass {
//...
}
}
}
- 不能有静态成员
- 不能用权限修饰符和static修饰符
- 作用域为所在方法内
- 只能访问所在方法中的final变量
- 如果所在方法为静态方法,则不能直接访问外部类的非静态成员
匿名内部类
- 继承父类
abstract class Parent {
public abstract void test();
}
class Test {
public static void main(String[] args) {
//父类引用指向匿名内部类对象
Parent p = new Parent() {
public void test() {
System.out.println("匿名内部类继承Parent类");
}
};
p.test();
}
}
- 实现接口
interface Flyable {
void fly();
}
class Test {
public static void main(String[] args) {
//接口引用指向匿名内部类对象
Flyable f = new Flyable() {
public void fly() {
System.out.println("匿名内部类实现Flyable接口");
}
};
f.fly();
}
}