Java 面向对象编程(高级)
Java 面向对象编程(高级)
sinarcsinx类变量和类方法
类变量
类变量:也叫 静态变量/静态属性。是该类所有对象共享的变量。任何一个该类对象访问时都是相同的值,任何一个该类对象修改时也是同一个变量。
语法(推荐):
访问修饰符 static 数据类型 变量名;
或者也可以:
static 访问修饰符 数据类型 变量名;
根据 JDK 版本的不同,类变量存放在 堆 中或 方法区 中。
什么时候需要用类变量:
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)
类变量 与 实例变量(普通属性)的区别:
类变量 是该类所有对象共享的,而 实例变量 是每个对象独享的
加上
static
称为 类变量 或 静态变量。否则称为 实例变量/普通变量/非静态变量静态变量 可以通过
类名.类变量名;
或对象名.类变量名;
来访问。但 Java 设计者推荐我们用类名.类变量名;
来访问。(需满足访问权限和范围)类变量 是在加载类时就初始化了。所以,没有创建对象实例也能访问。
类变量 的生命周期是随着 类的加载 开始,随着 类的消亡 而销毁。
特别地:一个 null 对象也可以访问静态变量 / 静态方法
1
2
3
4
5
6
7
8
9
10
11
12public class Test{
static int n = 0;
static void met() {
System.out.println(++n);
}
public static void main(String[] args){
Test t = null;
System.out.println(t.n); //这样不会报错
t.met(); //这样也不会报错
}
}
类方法
当方法使用
static
修饰后,就是 静态方法。静态方法就能访问静态属性。如果我们不希望创建实例,也能调用方法,这个场合把方法做成静态方法是合适的。开发工具类时就可以如此做。
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在 方法区。
- 类方法中不允许使用和对象有关的关键字。所以,类方法没有
this
或super
- 类方法可以通过类名调用,也能通过对象名调用。普通方法不能通过类名调用。
- 类方法 中只能访问 类变量 或 类方法
- 普通方法既可以访问普通方法也可以访问类方法
理解 main
方法语法
public static void main(String[] args){...}
main
方法 是 JVM 调用的方法。所以该方法的 访问权限 必须为public
JVM 在执行
main
方法时不必创建对象,所以main
方法 必须为static
该方法接收
String
类型的数组参数。该数组中保存执行 Java 命令 时传递给所运行的类的参数。工作台中:
javac 执行的程序.java
java 执行的程序 参数1(arg[0]) 参数2(arg[1]) 参数3(arg[2]) ..
在
main
方法 中,我们可以直接调用main
方法 所在类的静态方法或静态属性。但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例后才能通过该实例访问非静态成员。
代码块
代码块:又称为 初始化块。属于类中的成员。类似于方法,将逻辑语句封装在方法体中,通过
{ }
包围起来。和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类 显式调用,而是加载类时,或创建对象时 隐式调用。
语法:
[修饰符]{代码};
- 修饰符 是可选项,可不写。要写的话,只能写
static
- 代码块分为两类:
- 静态代码块:有
static
- 普通代码块:无
static
- 静态代码块:有
- 逻辑语句可以为任意的逻辑语句。
;
可以写,也可以省略。建议写上。- 代码块相当于另一种形式的构造器(构造器的补充机制),可以做初始化操作
- 如果多个构造器中都有重复语句,就可以抽取到初始化块中,提高代码复用率。这样,不管用哪个构造器,都会执行代码块。
使用细节
static
代码块:作用是对类进行初始化。随着 类的加载 会且只会执行一次。相对的:普通代码块每创建一个对象就执行一次。
类什么时候被加载?
创建对象实例时(new)
创建子类对象实例,父类也会加载
使用类的静态成员时(父类也会加载)
以上情况下类会被加载。加载后不需要再次加载,所以,静态代码块也只会执行一次。
创建一个对象时,在 一个类里 调用顺序是:
- 调用静态代码块 和 静态属性初始化。这两者优先级相同,多个存在时按照定义的顺序依次执行。
- 调用普通代码块 和 普通属性初始化。这两者优先级也相同。
- 调用构造器。
构造器
的最前面其实隐含了super();
和调用普通代码块
。而静态相关的代码块,属性初始化,在类加载时就执行完毕了。这样,创建一个对象时,在 有继承关系的多个类里 调用顺序是:
- 父类 静态代码块 和 静态初始化
- 子类 静态代码块 和 静态初始化
- 父类 普通代码块 和 普通初始化
- 父类 构造器
- 子类 普通代码块 和 普通初始化
- 子类 构造器
静态代码块 只能调用 静态成员。普通代码块 能调用 任意成员。
单例设计模式
什么是设计模式:设计模式是在大量的实践中总结和理论化后优选的代码结构、编程风格、解决问题的思考方式。设计模式就像是经典的棋谱,免去我们自己再思考和摸索。
单例设计模式:采取一定的方法,保证再整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
应用实例
后面会学更多,这里先展示两种:饿汉式、懒汉式
饿汉式
步骤如下:
构造器私有化(防止用户直接 new)
类的内部创建对象
向外暴露一个静态的公共方法
代码实现
1
2
3
4
5
6
7
8
9
10class GF{
private String name;
private static GF gf = new GF("萝茵");
private GF(String name){
this.name = name;
}
public static GF getGF(){
return gf;
}
}对象,通常都是重量级的对象
有时,我们用不到这个创建的对象,那个场合,会造成资源浪费。
懒汉式
步骤如下:
构造器私有化
定义一个静态属性对象
提供一个静态的公共方法,可以返回对象。如果静态对象为空,则创建对象
代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13>class GF{
private String name;
private static GF gf;
private GF(String name){
this.name = name;
}
public static GF getGF(){
if(gf == null){
gf = new GF("萝茵");
}
return gf;
}
>}
两种方法对比
- 二者创建对象的时机不同。饿汉式在加载类信息时创建,懒汉式在使用时才创建
- 饿汉式可能造成资源浪费,懒汉式可能存在线程安全问题(学习[线程]后会进行完善)。
- Java SE 标准类中 java.lang.Runtime 就是一个单例模式。
final 关键字
final
可以修饰 类、属性、方法、局部变量以下情况下,可能用到
final
final
修饰类:该类不能被继承final
修饰方法:该方法不能被重写final
修饰值:该值不能被修改
使用细节
final
修饰的属性又叫常量,一般用 XX_XX_XX 来命名(全大写字母+下划线)final
修饰的属性在定义时,必须赋初始值,且之后不能再修改。赋值可以在下列位置之一:- 定义时
- 构造器中
- 代码块中
注意:如果
final
修饰的属性是静态的,则只能在以下位置赋值。- 定义时
- 静态代码块中
final
类不能继承,但能实例化对象。对的,是可以的。如果不是
final
类,但含有final
方法,虽然该方法不能重写,但能被继承。final
类可以有final
方法。可以,但没必要。final
不能修饰构造方法。final
和static
搭配使用,效率更高(那个场合,虽然顺序不限,还是推荐static
在前)。底层编译器做了优化处理。这样做,调用 属性(定义时赋值) 时居然 不会造成类的加载!包装类(Integer、Double、Float、Boolean、String等)都是
final
类,都不能被继承。
抽象类
当父类的某些方法需要声明,却不知道如何实现时,可以将其声明为抽象方法。那个场合,要将该类声明为
abstract
类。抽象类的价值更多是用于设计。设计者设计好后,让子类继承并实现。也是考官爱问的考点。
定义抽象类:
访问修饰符 abstract 类名{...}
定义抽象方法(注意:无方法体):
访问修饰符 abstract 返回值 方法名(形参列表);
使用细节
- 抽象类不能被实例化
- 抽象类不一定包含抽象方法。也就是说,抽象类可以没有
abstract
方法 - 一旦包含
abstract
方法,则该类一定要声明为abstract
abstract
只能修饰 类 和 方法,不能修饰其他。- 抽象类可以有任意成员(非抽象方法、构造器、静态属性等)。即,抽象类本质还是类。
- 抽象方法不能有主体。即,抽象方法不能实现。
- 如果一个类继承了
abstract
类,则其必须实现所有abstract
方法,除非其自己也是abstract
类。 - 抽象方法不能用
private
final
static
来修饰。因为,这些关键词都和 重写 相违背。
接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要用的时候,再根据具体情况把这些方法写出来。
语法:
interface 接口名{...}
class 类名 implements 接口名{...必须实现接口的抽象方法...}
注意:JDK 7.0 以前,接口中只能是抽象方法。而 JDK 8.0 后,接口可以有静态(
static
)方法、默认(default
)方法。在接口中,抽象方法可以省略
abstract
接口中可以存在:
- 属性(只有静态
static
属性,可以不加static
关键字) - 方法(抽象
abstract
方法、默认default
实现方法、静态static
方法)
使用细节
- 接口 不能被实例化。
- 接口中所有方法都是
public
方法。接口中的 抽象方法 可以不用abstract
修饰。 - 一个普通类实现接口,就必须把该接口所有方法都实现。(用快捷键吧
alt + enter
) - 抽象类实现接口,可以不用实现接口的方法。
- 一个类可以同时实现多个接口。
class Name implements In1,In2{...}
- 接口中的属性只能是
final
的,并且是public static final
修饰符。修饰符就算不写,还是这样。 - 接口中属性的访问形式:
接口名.属性名
- 接口不能 继承 其他的类,但可以 继承 多个别的接口。(不是也不能 实现 别的接口)
- 接口的修饰符只能是
public
和 默认。这点和类的修饰符相同。
实现接口 vs 继承类
- 当子类继承父类,就自动拥有父类的所有功能。如果需要扩展功能,可以通过接口方式扩展。
- 可以认为,接口 是对于 Java 单继承机制的补充。
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值主要在于:设计。设计好各种规范,让其他类去实现这些方法。
- 接口比继承更加灵活。继承需要满足 is - a 的关系,而接口只需要满足 like - a 关系。
- 接口在一定程度上实现代码解耦。(即:接口规范性 + 动态绑定机制)
接口的多态特性
多态参数(接口的引用可以指向实现了接口的类的对象)
viod work(Inerface01 i1){...}
参数可以传入任意实现该接口的类多态数组
接口存在多态传递现象
接口中的static/default
接口中的default方法,可以写方法内容。
接口中的default方法不强制实现类重写,不会影响到已有的实现类。
接口的实现类,可以调用接口中的default方法
接口中的static方法,可以写方法内容。
接口中的static方法不允许实现类重写(如果重写方法加上@Override会报错),不会影响到已有的实现类。
接口中的static方法,可以直接通过接口名称调用
细节
- default
- 当继承的父类和实现的接口中有相同签名的方法时,优先使用父类的方法。
- 当接口的父接口中也有同样的默认方法时,就近原则调用子接口的方法。
- 当实现的多个接口中有相同签名的方法时,必须在实现类中通过重写方法解决冲突问题,否者无法通过编译,在重写的方法中可以通过 接口名.super.方法名(); 的方式显示调用需要的方法。
- static
- 接口中的数据对所有实现类只有一份,所以是static
- 要使实现类为了向上转型成功,所以必须是final的(接口不能被实例化,所以接口里面如果是变量的话不会被赋初始值这样就会出问题,所以必须是final的。其实还是为了安全考虑的)
内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类被称为 内部类。
1
2
3
4
5
6 class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}内部类的最大特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
四种内部类
分别是:
- 定义在外部类的局部位置上
- 局部内部类:有 类名
- 匿名内部类:无 类名
- 定义在外部类的成员位置上
- 成员内部类:无
static
修饰 - 静态内部类:
static
修饰的类
- 成员内部类:无
局部内部类
局部内部类:定义在外部类的局部位置上,并且有类名。(局部位置?比如:方法/代码块里)
1
2
3
4
5
6 class Outer { //外部类
public void tools01() {
class Inner { //局部内部类
}
}
}
使用细节
定义在外部类的局部位置上,并且有类名。
可以访问外部类的所有成员,包含私有成员
局部内部类可以 直接访问 外部类的成员。
不能添加 访问修饰符,因为其地位相当于局部变量。但,可以使用
final
,因为局部变量也能用final
作用域 仅仅在定义它的方法或代码块中
外部类 在方法中,可以创建 局部内部类 的对象实例,然后调用方法。
外部其他类 不能访问 局部内部类
如果外部类和局部内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用
外部类名.this.变量名
外部类名.this
本质就是 外部类的对象。即,调用了该方法(上例的tools01
)的对象
匿名内部类
匿名内部类:定义在外部类的局部位置,且没有类名
1
2
3 >new 类/接口 (参数列表) {
类体
>}匿名内部类本质是没有名字的类,而且是内部类。同时,还是一个对象。
可以用匿名内部类简化开发
一个例子
1
2
3
4
5
6
7
8 >class Outer { //外部类
public void tools01() {
Inter whatEver = new Inter(){ //匿名内部类
};
}
>}
>interface Inter{
>}其实,这个匿名内部类
new Inter(){}
的运行类型就是class XXXX implements Inter
。系统自动分配的名字是Outer$1
(whatEver.getClass = "Outer$1"
)JDK 在创建匿名内部类
Outer$1
时,立即创建了一个对象实例,并将地址返回给了whatEver
匿名内部类使用一次后就不能再次使用(
Outer$1
就这一个了)
使用细节
- 匿名内部类语法比较独特。其既是一个类的定义,也是一个对象。因此,从语法上看,其既有 定义类的特征,也有 创建对象的特征。
- 可以访问外部类的所有成员,包括私有的。
- 局部内部类可以 直接访问 外部类的成员。
- 不能添加 访问修饰符,因为其地位相当于局部变量。但,可以使用
final
,因为局部变量也能用final
- 作用域:仅仅在定义它的方法或方法快中
- 外部其他类 不能访问 匿名内部类
- 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用
外部类名.this.变量名
使用场景
当作实参直接传递,简洁高效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Homework1 {
public static void main(String[] args) {
new Cellphone().clock(new Bell() { //看这里看这里
public void belling() {
System.out.println("小懒猪起床了!");
}
});
}
}
interface Bell {
void ringing();
}
class Cellphone{
public void clock(Bell bell){
bell.ringing();
}
}
成员内部类
成员内部类:定义在外部类的成员位置,并且没有
static
修饰。
1
2
3
4 class Outer{
class Inner{
}
}
使用细节
- 可以直接访问外部类的所有成员,包括私有的
- 可以添加任意访问修饰符。因为,成员内部类的地位就是一个成员。
- 作用域 和外部类其他成员相同,为整个类体。
- 局部内部类可以 直接访问 外部类的成员。
- 外部类可以通过创建对象的方式访问成员内部类
- 外部其他类访问成员内部类
Outer.Inner name = Outer.new Inner();
下个方法的缩写Outer.Inner name = new Outer().new Inner();
- 在外部类中编写一个方法,返回一个
Inner
的对象实例(就是对象的 getter)
- 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用
外部类名.this.变量名
静态内部类
静态内部类:定义在外部类的成员位置,经由
static
修饰。
1
2
3
4 class Outer{
static class Inner{
}
}
使用细节
- 可以直接访问外部类的所有 静态 成员,包括私有的。但不能访问非静态成员
- 可以添加访问修饰符。因为,静态内部类的地位就是一个成员。
- 作用域 和其他成员相同,为整个类体。
- 静态内部类可以 直接访问 外部类的成员。
- 外部类可以通过创建对象的方式访问静态内部类
- 外部其他类访问静态内部类
Outer.Inner name = new Outer.Inner();
即通过类名直接访问- 在外部类中编写一个方法,返回一个
Inner
的对象实例 - 如果外部类和匿名内部类的成员重名时,默认遵循就近原则。那个场合,访问外部类成员使用
外部类名.变量名
。(怎么不一样了呢?因为静态内部类访问的都是静态成员)