9. 面向对象编程(高级)
9.1 类变量/静态变量和类方法/静态方法
9.1.1 类变量/静态变量
特点:
- static变量/类变量是由同一个类的所有的对象共享。
- static类变量,在类加载的时候就生成了。
语法:
访问修饰符 static 数据类型 变量名;
static 访问修饰符 数据类型 变量名;
类变量的调用
- 类名.类变量名
- 对象名.类变量名
类方法/静态方法
特点:
-
(静态方法/类方法) 不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
2.(类方法/静态方法)在类加载的时候加载
语法:
访问修饰符 static 数据返回类型 方法名(){}
static 访问修饰符 数据返回类型 方法名(){}
类方法的调用
- 类名.类方法名
- 对象名.类方法名
两者特点
类变量和类方法是可以被继承的,但是类方法是不可以被重写的,静态变量和静态方法都是在类加载的时候就加载了。
9.2 理解main方法语法
9.2.1解释main方法的形式:public static void main(String[] args){}
- main方法是由虚拟机调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递运行的类的参数
- java 执行的程序 参数1 参数2 参数3
9.3 代码块
9.3.1语法
[修饰符]{
}
9.3.2 分类
- 局部代码块。
- 构造代码块。
- 同步代码块。
- 静态代码块。
9.3.2.1 局部代码块
在方法中出现,可以限定变量生命周期,及早释放,提高内存利用率。
9.3.2.2 构造代码块
在类中方法外出现,每次调用构造方法都会执行,并且在构造方法前执行。
9.3.2.3 同步代码块
同步代码块指的是被Java中Synchronized关键词修饰的代码块,在Java中,Synchronized关键词不仅仅可以用来修饰代码块,与此同时也可以用来修饰方法,是一种线程同步机制,被Synchronized关键词修饰的代码块会被加上内置锁。
需要说明的是Synchronized同步代码块是一种高开销的操作,因此我们应该尽量减少被同步的内容,在很多场景,我们没有必要去同步整个方法,而只需要同步部分代码即可,也就是使用同步代码块(JDK源码中有很多应用)。
9.3.2.4 静态代码块
在类中方法外出现,并加上static修饰,常用于给类进行初始化,在加载的时候就执行,并且静态代码块执行一次。
9.3.4特点
静态代码块 | 构造代码块 |
---|---|
在类加载JVM时初始化,且只被执行一次;常用来执行类属性的初始化;静态块优先于各种代码块以及构造函数;此外静态代码块不能访问普通变量。 | 每次调用构造方法,构造代码块都执行一次;构造代码块优先于构造函数执行;同时构造代码块的运行依赖于构造函数。 |
9.4 Final使用注意事项
- final修饰的属性又叫常量,一般用XX_XX_XX来命名。
- final修饰的属性在定义时,必须赋初值,并且以后不能修改,赋值可以在如下位置
- 定义时,如public final double TAX_RATE = 0.08
- 在构造器中
- 在代码块中
- 如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块中,不能在构造器中赋值
- final类不能被继承,但是可以实例化对象。
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
- final修饰的方法不能被重写
- final不能修饰构造方法
- 包装类(Integer,Double,Float,Boolean等都是final),String类也是final类
- final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化
9.5 抽象
抽象类
语法
访问修饰符 abstract 类名{}
特点
抽象类不能被实例化
抽象类不一定要包含abstarct方法
抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等。
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
抽象类不能使用final修饰符
抽象方法
语法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
特点
抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
接口
语法:
interface 接口名{
//属性
//抽象方法
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法;
}
特点:
-
接口是更加抽象的抽象类,抽象类里面的方法可以有方法体,接口里所有的方法都没有方法体(jdk7.0),JDK8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。
-
接口和抽象类一样,都是不能被实例化的。
-
接口中的所有方法如果不是默认方法和static方法的话,剩下的全是public的抽象方法,接口中抽象方法,可以不用abstract修饰。接口中的抽象方法也只能用public修饰
void aaa(); //实际上是 public abstract void aaa();
-
一个普通类实现接口,就必须将该接口的所有抽象方法都实现
-
抽象类实现接口,可以不用实现接口的方法。
-
一个类同时可以实现多个接口
-
接口中的属性,只能是final的,而且是public static final 修饰符。
//例如 int a = 1; //实际上是 public static final int a = 1;
-
接口不能继承其他的类,但是可以继承其他的多个接口。
-
interface A extends B,C()
-
-
接口的修饰符只能是public和默认,这点和类的修饰符是一样的。
内部类
局部内部类
局部内部类定义在外部类的局部位置,如方法中和代码块中,并且有类名。
特点
- 可以直接访问外部类的所有成员,包含私有的,也可以访问方法内的局部变量。
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰。因为局部变量也可以使用final。被final修饰的局部内部类不能被继承
- 作用域:仅仅在定义它的方法或代码块中
- 外部类无法直接使用,需要在方法内部创建对象并使用。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,这可以使用**(外部类名.this.成员)**去访问。
语法
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass {
public static void main(String[] args) {
//演示一遍
Outer02 outer02 = new Outer02();
outer02.m1();
System.out.println("outer02的hashcode=" + outer02);
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
}//私有方法
public void m1() {//方法
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中
final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()
//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 使用 外部类名.this.成员)去访问
// Outer02.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer02.this就是哪个对象
System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashcode=" + Outer02.this);
m2();
}
}
//6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类
匿名内部类定义在外部类的局部位置,如方法中和代码块中,也可以作为参数传递,并且没有类名。
匿名内部类是将(继承/实现)(方法重写)(创建对象)三个步骤,放在了一步进行。
语法
new 类或接口(参数列表){
类体
};
代码
package com.zjw.InnerClass;
/**
* @Description 匿名内部类
* @Author zhaojiawei
* @Since 2021-11-22
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 {
private int n1 = 10;
public void method(){
//基于接口的匿名内部类
//1.需求,想使用IA接口,并创建对象
//2.传统方式,是写一个类,实现该接口,并创建对象
//3.类只使用一次的话,可以使用匿名内部类来简化开发
//4.tiger的编译类型 ?IA 其实就是接口类型
//5.tiger的运行类型 ?就是匿名内部类
/**
* 我们看底层
* class XXXX implements IA{
* @Override
* public void cry() {
* System.out.println("老虎叫唤");
* }
* }
*/
//6.jdk底层在创建匿名内部类Outer04$1,立即马上就创建了Outer04$1实例,并且把地址
//返回给tiger
//匿名内部类使用一次,就不能再使用了
//其实就是实现了Inner接口的,一个实现类对象
IA tiger = new IA() {
@Override
public void cry() {
}
};
System.out.println(tiger.getClass());
tiger.cry();
//演示基于类的匿名内部类
//分析
//1.Father的编译类型 father
//2.Father的运行类型 Outer04$2
//3.底层会创建匿名内部类
/*
* class Outer04$2 extends Father{
*
* }
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2的对象
Father father = new Father("Jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了Father的Test()方法");
}
};
System.out.println("father对象的运行类型"+ father.getClass());
father.test();
//基于抽象类的匿名内部类
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
interface IA{
public void cry();
}
class Tiger implements IA{
@Override
public void cry() {
System.out.println("老虎叫");
}
}
class Dog implements IA{
@Override
public void cry() {
System.out.println("🐶叫");
}
}
class Father {
public Father(String name) {
super();
}
public void test(){
}
}
abstract class Animal{
abstract void eat();
}
静态内部类
静态内部类时定义在外部类的成员位置,并且由static修饰
特点
- 可以直接访问外部类的所有静态成员,包含私有的,但不能访问非静态成员
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
- 作用域:同其他的成员,为整个类体
- 静态内部类访问外部类,直接访问所有静态成员(也只能访问静态成员)
- 外部类访问静态内部类,访问方式:创建对象,再访问
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。
代码
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.m1();
//外部其他类 使用静态内部类
//方式1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
//方式2
//编写一个方法,可以返回静态内部类的对象实例.
Outer10.Inner10 inner101 = outer10.getInner10();
System.out.println("============");
inner101.say();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("************");
inner10_.say();
}
}
class Outer10 { //外部类
private int n1 = 10;
private static String name = "张三";
private static void cry() {}
//Inner10就是静态内部类
//1. 放在外部类的成员位置
//2. 使用static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
private static String name = "韩顺平教育";
public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
System.out.println(name + " 外部类name= " + Outer10.name);
cry();
}
}
public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
Inner10 inner10 = new Inner10();
inner10.say();
}
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
成员内部类
成员内部类时定义在外部类的成员位置,并且没有static修饰。
特点
- 可以直接访问外部类的所有成员,包含私有的
- 可以添加任务访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域:为整个类体。
- 成员内部类访问外部类:直接访问
- 外部类访问成员内部类:创建对象,再访问
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
代码
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的二种方式
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是outer08成员
// 这就是一个语法,不要特别的纠结.
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 { //外部类
private int n1 = 10;
public String name = "张三";
private void hi() {
System.out.println("hi()方法...");
}
//1.注意: 成员内部类,是定义在外部内的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
public class Inner08 {//成员内部类
private double sal = 99.8;
private int n1 = 66;
public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
hi();
}
}
//方法,返回一个Inner08实例
public Inner08 getInner08Instance(){
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
创建一个子类时(继承关系)的执行顺序(重点)
- 父类的静态代码块和静态属性(优先级按定义顺序执行)
- 子类的静态代码块和静态属性(优先级按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法