Java笔记——面向对象编程(高级)
类变量和类方法
类变量
static变量 被类的所有对象实例共享 jdk8以后,静态变量在内存的堆中 class实例的尾部 共识:
- static变量是同一个类所有对象共享
- static类变量,在类加载的时候就生成了,没创建实例也能访问
定义:访问修饰符 static 数据类型 变量名
访问:类名.类变量名
使用细节
- 什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):例如:定义学生类,统计所有学生共交了多少钱。
Student(name, static fee) - 类变量与实例变量(普通变量)区别 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
- 实例变量不能通过
类名.类变量名方式访问 - 类变量是在类加载时就初始化了,即使没创建对象,只要类加载了,就可以使用类变量
- 类变量的生命周期时随类的加载开始,随类的消亡而销毁
类方法
定义:访问修饰符 static 数据返回类型 方法名(){ }
访问:类名.类方法名
前提满足访问修饰符的访问权限和范围
使用场景:当方法不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,不需要创建对象就可以使用,提高开发效率。比如:工具类中的方法utils,
Math类,Arrays类,Collections集合类
使用细节
- 类方法和普通方法都是随类的加载而加载,将结构信息存储在方法区:
类方法中无
this的参数,普通方法中隐含this的参数 - 类方法可以通过类名调用,也可以通过对象名调用
- 类方法中不允许使用和对象有关的关键字,比如
this和super,普通方法(成员方法)可以。 - 类方法中只能访问静态变量或静态方法,普通成员方法还能访问普通变量和方法
理解main方法语法
public static void main(String[] args) 1.
Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须时public 2.
Java虚拟机在执行main()方法时不必创建对象,所以该方法必须时static 3.
该方法在接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
4. java 执行的程序 参数1 参数2 参数3 ...
特别提示:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
代码块
代码块又称初始化块,属于类中的成员,即类的一部分,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法 [static] { 代码 }
代码块的好处
- 相当于另一种形式的构造器(对构造器的补充机制),可以做初始化操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
代码块使用注意事项和细节
- static代码块也叫静态代码块,作用是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
- 类被加载的三种情况(重要)
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性,静态方法)
- 普通代码块,在创建对象实例时,会被隐式的调用。如果只是使用类的静态成员,普通代码块不会被执行。
- 创建一个对象时,在一个类中调用顺序:(重要)
- 调用静态代码块和静态属性初始化(优先级一样,顺序调用)
- 调用普通代码块和普通属性的初始化 (优先级一样)
- 调用构造方法
1 | |
- 构造器(构造方法)的最前面其实隐含了super()和调用普通代码块;静态相关的代码块、属性初始化,在类加载时就执行完毕。因此是优先于构造器和普通代码块执行的。
1
2
3
4
5
6
7class A {
public A() {
// super();
// 调用普通代码块
System.out.println("ok");
}
} - 创建一个子类时,调用顺序: 父静 -> 子静 -> 父普 -> 父构
-> 子普 -> 子构
1
2
3
4// (1) 进行类的加载
// 1.1 先加载父类 1.2 再加载子类
// (2) 创建对象
// 2.1 从子类构造器开始 - 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员
单例设计模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法 步骤: 1. 构造器私有化 (防止直接new) 2. 类的内部创建对象 3. 向外暴露一个静态的公共方法(getInstance)
饿汉式
没调用也会创建对象 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32package com.company.single_;
public class SingleInstance01 {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
}
}
class GirlFriend {
private String name;
// 1. 将构造器私有化
private GirlFriend(String name) {
this.name = name;
}
// 2. 类内创建静态对象
private static GirlFriend joe = new GirlFriend("Jocelyn");
// 3. 静态方法返回对象引用
public static GirlFriend getInstance() {
return joe;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
懒汉式
1 | |
饿汉式VS懒汉式
- 两种主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建;
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题;
- 饿汉式存在资源浪费的可能;(没有使用对象实例)
- 在JavaSE标准类中,
java.lang.Runtime是经典的单例模式;
final关键字
基本介绍
final可以修饰类、属性、方法和局部变量 1.
当不希望类被继承时,可以用final修饰; 2.
当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰;
3. 当不希望类的某个属性的值被修改,可以用final修饰; 4.
当不希望某个局部变量被修改,可以使用final修饰;
注意事项
- final修饰的属性又叫常量,一般用XX_XX来命名;
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值方式:
- 定义时;
- 在构造器中;
- 在代码块中;
- 如果final修饰的属性是静态的,则初始化的不能在构造器中赋值;
- final类不能继承,但是可以实例化;
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承;
- 一般来说,如果一个类已经是final类了,就没必要再将方法修饰成final方法;
- final不能修饰构造器
- final 和 static
往往搭配使用,效率更高,底层编译器做了优化处理;
1
2
3
4
5
6
7
8class A {
// 有final 静态代码块不会被提前加载
public final static int num = 100;
static {
System.out.println("类A被加载...");
}
} - 包装类(Integer, Double, Float, Boolean等都是final),String也是final类;
抽象类
- 父类方法的不确定性
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类,用abstract修饰;抽象类会被继承,由其子类实现具体方法
1
访问修饰符 abstract 返回类型 方法名(参数列表); // 没有方法体 - 抽象类的价值更多作用在于设计,是设计者设计好后,让子类继承并实现抽象类,在框架和设计模式使用较多 ## 使用细节和注意事项
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract 只能修饰类和方法,不能修饰属性和其它的
- 抽象类可以有任意成员【抽象类本质还是类】
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也申明为abstract类
- 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的
模板设计模式
设计一个抽象类(Template),能完成如下功能: 1.
编写一个方法calculateTime(),可以计算某段代码的耗时时间 2.
编写抽象方法job() 3. 编写一个子类Sub,继承抽象类Template,并实现job方法
4. 编写一个测试类TestTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package com.company.abstract_;
public class TestTemplate {
public static void main(String[] args) {
Sub sub = new Sub();
sub.calculateTime(); // 多态的动态绑定机制
}
}
abstract class Template {
public abstract void job(); // 抽象方法
public void calculateTime() {
long start = System.currentTimeMillis();
job(); // 调用 动态绑定机制
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
}
}
class Sub extends Template {
@Override
public void job() {
// 具体的工作
long num = 0;
for (int i = 0; i < 800000; i++) {
num += i;
}
}
}1
2
3
4
5
6
7
8
9
10interface 接口名 {
// 属性
// 方法
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法;
}int a = 1;实际上是public static final int a = 1;(必须初始化)
7. 接口中属性的访问形式:接口名.属性名 8.
一个接口不能继承其他类,但可以继承多个别的接口;interface A extends B, C {}
9. 接口的修饰符只能是public和默认,这点和类的修饰符是一样的; ##
实现接口 VS 继承类 1. 接口和继承解决的问题不同 -
继承的价值主要在于:解决代码的复用性和可维护性 -
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法
2. 接口比继承更加灵活
继承是满足is-a的关系,而接口只需满足like-a的关系
3. 接口在一定程度上实现代码解耦 【接口规范性+动态绑定】
1 | |
接口多态特性
- 多态参数
接口引用可以指向实现了接口的类的对象1
2
3
4
5
6
7
8public class Computer {
// 传入接口
public void work(UsbInterface usbInterface) {
// 通过接口调用方法
usbInterface.start();
usbInterface.stop();
}
} - 多态数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package com.company.interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camara_();
for (Usb usb : usbs) {
if (usb instanceof Phone_) ((Phone_) usb).call(); // 向下转型
usb.work(); // 动态绑定
}
}
}
interface Usb {
void work();
}
class Phone_ implements Usb {
@Override
public void work() {
System.out.println("手机工作中...");
}
public void call() {
System.out.println("---手机可以打电话---");
}
}
class Camara_ implements Usb {
@Override
public void work() {
System.out.println("相机工作中...");
}
} - 接口存在多态传递现象 # 内部类 ## 基本介绍 一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。内部类最大特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。 类的五大成员:属性,方法,构造器,代码块,内部类 ## 基本语法
1
2
3
4
5
6
7
8interface A {}
interface B extends A {}
class C implements B {
//xxx
}
B b = new C(); // 接口类型的变量指向实现该接口的类的对象实例
A a = new C(); // B 继承了 A, C相当于也实现了接口A## 内部类的分类1
2
3
4
5
6
7
8
9class Outer { // 外部类
class Inner { // 内部类
}
}
class Other { // 外部其他类
}
- 定义在外部类局部位置上(比如方法内, 代码块内):
- 局部内部类(有类名)
- 匿名内部类(没有类名,重点!!!)
- 定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类
- 可以直接访问外部类的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类 - 访问 - 外部类的成员 【访问方式:直接访问】
- 外部类 - 访问 - 局部内部类的成员 【访问方式:创建对象再访问】
- 外部其他类 - 不能访问 - 局部内部类
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可使用
外部类名.this.成员去访问 【外部类名.this本质是外部类的对象,谁调用就指谁】 ### 匿名内部类 本质是类,内部类,该类没有名字,同时还是一个对象1
2
3new 类或接口(参数列表) {
类体
};
- 匿名内部类的使用
匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36package com.company.innerclass_;
public class InnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.f1();
}
}
class Outer {
private int n1 = 10;
public void f1() {
// class 匿名内部类 extends Preson {}
Person p = new Person() { // 创建一个基于类的匿名内部类
@Override
public void hi() {
System.out.println("匿名内部类重写了hi()方法");
}
};
System.out.println(p.getClass());
p.hi(); // 动态绑定,运行类型是 Outer$1
new Person() { // 直接调用
@Override
public void hi() {
System.out.println("匿名内部类重写了hi()方法");
}
}.hi();
}
}
class Person {
public void hi() {
System.out.println("person.hi()");
}
}
1 | |
1 | |
成员内部类
成员内部类是定义在外部类的成员位置,并且没有static修饰。 1.
可以直接访问外部类的所有成员,包括私有的 2.
可以添加任意访问修饰符,因为它的地位就是一个成员 3.
作用域和外部类的其他成员一样,为整个类体。 4. 成员内部类 - 访问 - 外部类
【访问方式:直接访问】 5. 外部类 - 访问 - 成员内部类
【访问方式:创建对象,再访问】 6. 外部其他类 - 访问 - 成员内部类 
静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰。
1.
可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员
2. 可以添加任意访问修饰符(public, protected, default, private) 3.
作用域:同其他成员,为整个类体 4. 静态内部类 - 访问 - 外部类
【访问方式:直接访问所有静态成员】 5. 外部类 - 访问 - 静态内部类
【访问方式:创建对象,再访问】 6. 外部其他类 - 访问 - 成员内部类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48package com.company.innerclass_;
public class StaticInnerClass {
public static void main(String[] args) {
new Outer_().m1();
// 其他外部类使用静态内部类
// 方式1 【直接类名访问】
Outer_.Inner inner = new Outer_.Inner();
inner.say();
// 方式2 【编写一个方法,返回静态内部类的对象实例】
Outer_ outer_ = new Outer_();
Outer_.Inner inn = outer_.getInner();
inn.say();
// 方式3 【编写一个静态方法,返回静态内部类的对象实例】
Outer_.Inner innerInstance = Outer_.getInnerInstance();
innerInstance.say();
}
}
class Outer_ {
private int n1 = 10;
private static String name = "张三";
public static void cry() {
System.out.println("cry()...");
}
protected static class Inner {
public void say() {
System.out.println(name);
cry();
}
}
public void m1() {
Inner inner = new Inner();
inner.say();
}
public static Inner getInnerInstance() {
return new Inner();
}
public Inner getInner() {
return new Inner();
}
}外部类名.成员去访问
【注意没有this】