8 面向对象(高级)
文章目录
8.1 类变量和类方法⭐️
8.1.1 类变量
-
基本介绍:
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量
-
基本语法:
访问修饰符 static 数据类型 变量名; static 访问修饰符 数据类型 变量名;
-
如何访问类变量:
- 类名.类变量名
- 对象名.类变量名
- 静态变量的访问修饰符的访问权限和范围和普通属性是一样的
-
内存布局:
-
注意事项和细节:
- 类变量是该类的所有对象共享的,而实例变量是每个对象独享的
- 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 类变量可以通过 类名.类变量名 或者 对象名.类变量名来访问
- 实例变量不能通过类名.类变量名方式访问
- 类变量是在类加载时就初始化
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
8.1.2 类方法
-
基本介绍:
类方法也叫静态方法
-
基本语法:
访问修饰符 static 数据返回类型 方法名(){}; static 访问修饰符 数据返回类型 方法名(){};
-
类方法调用:
- 类名.类方法名
- 对象名.类方法名
- 满足访问修饰符的访向权限和范围
-
使用场景:
- 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
- 程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用
-
注意事项和细节:
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数,普通方法中隐含着this的参数 - 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
- 类方法中不允许使用和对象有关的关键字,比如this和super
- 静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
8.2 main方法
-
深入理解main方法:
- java虛拟机需要调用类的main0方法,所以该方法的访问权限化须是public
- java虚拟机在执行main0方法时不必创建对象,所以该方法心须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- java 执行的程序 参数1 参数2 参数3

-
注意事项和细节:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的目静态成员
8.3 代码块
-
基本介绍:
代码化块又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过们包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用
-
基本语法:
[修饰符]{ 代码; }
-
注释:
- 修饰符 可选,要写的话,也只能写 static
- 代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块
- 逻辑语句可以为任何逻辑语句
- :号可以写上,也可以省略
-
注意事项和细节:
- static代码块是类加载时执行,且只会执行一次
- 普通代码块是在创建对象的调用的,创建一次,调用一次
- 类什么时候被加载⭐️⭐️:
- 创建对象实例时
- 创建子类对象实例时,父类也会被加载
- 使用类的静态成员时
- 创建一个对象时,在一个类的调用顺序:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级样,如果有多个静态代码块和多个静
态变量初始化,则按他们定义的顺序调 - 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,若果有多个普通代码块和stat多个普通属性初始化,则按定义顺序调用)
- 调用构造器
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级样,如果有多个静态代码块和多个静
- 构造方法(构造器)的最前面其实隐含了 super()和调用普通代码块
- 创建子类时顺序⭐️⭐️:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类构造方法
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
8.4 单例设计模式
-
基本介绍:
就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种方式:1. 饿汉式 2.懒汉式
-
饿汉式:
-
构造器私有化
-
类的内部创建静态对象
-
向外暴露一个静态公共方法
-
特点:在类加载的时候就创建对象实例,可能存在资源浪费
class GirlFriend{ private String name; private static GirlFriend gf = new GirlFriend("小花"); private GirlFriend(String name) { this.name = name; } public static GirlFriend Instance() { return gf; } }
-
-
懒汉式:
-
构造器私有化
-
定义一个static静态属性对象
-
提供一个public的static方法,返回一个对象
-
只有当使用(3)的方法时,才返回对象,再次调用时,返回上次创建的对象
-
特点:线程安全问题
class Cat{ private String name; private static Cat cat; private Cat(String name) { this.name = name; } public static Cat getInstance(){ if(cat == null){ cat = new Cat("小花"); } return cat; } }
-
-
对比:
- 最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,徽汉式存在线程安全问题
- 饿汉式存在浪费姿源的可能,对象实例都没有使用,那么饿汉式创建的对象就浪费,懒汉式是使用时才创建,就不存在这个问题
8.5 final
-
基本介绍:
-
final可以修饰类、属性、方法、局部变量
-
final 类,类不能被继承
-
final 方法,子类无法重写方法
-
final 属性,属性无法被修改
-
final 局部变量,局部变量无法被修改
-
-
注意事项和细节:
- final修饰的属性又叫常量,一般用XX_XX_XX 来命名
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置:
定义时、构造器、代码块 - final修饰的属性是静态的,则初始化的位置只能是:定义时、静态代码块
- final类不能继承,但可以实例化对象
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
- 如果一个类已经是final类了,就没有必要再将方法修饰成final方法
- final不能修饰构造器本身
- final 和static 往往搭配使用,效率更高,不会导致类加载-底层编译器做了优化处理
- 包装类(Integer, Double,Float,Boolean等都是final),String也是final类
8.6 抽象类
-
基本介绍:
-
用abstract 关键字来修饰一个类时,这个类就叫抽象类
-
用abstract 关键字来修饰一个方法时,这个方法就是抽象方法,没有方法体
-
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
-
-
注意事项和细节:
- 抽象类不能被实例化
- 抽象类可以没有abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract 只能修饰类和方法
- 抽象类可以有任意成员,抽象类本质还是类
- 抽象方法不能有主体
- 如果一个类继承了抽象类,则它必须实现抽象类的所有的抽象方法,除非它自己也声明为abstract类
- 抽象方法不能使用private、final 和static来修饰,因为这些关键字都是和重写相违背的
-
抽象类模版设计模式:
- 编写方法cal(),可以计算某段代码的耗时时间
- 编写抽象方法job()
- 编写一个子类A,继承抽象类Template,井实现job方法
- 编写一个测试类TestTemplate,看看是否好用
public class TextTemplate { public static void main(String[] args) { A a = new A(); a.cal(); B b = new B(); b.cal(); } } abstract public class Template { public abstract void job(); public void cal() { long start = System.currentTimeMillis(); job(); long end = System.currentTimeMillis(); System.out.println("时间" + (end - start)); } } class A extends Template{ public void job() { long num = 0; for (long i = 0; i <= 10000000; i++) { num += i; } } } class B extends Template{ public void job() { long num = 0; for (long i = 0; i <= 10000000; i++) { num *= i; } } }
8.7 接口⭐️
-
基本介绍:
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来
-
基本语法:
interface 接口名{ //属性 //方法 } class 类名 implements 接口 { 自己属性; 自己方法; //必须实现接口的抽象方法 }
- Jdk7.0前 接口里的所有方法都没有方法体(只能抽象方法)
- Jdk8.0后接口类可以有静态方法,defaut实现方法,也就是说接口中可以有方法的具体实现
-
注意事项和细节:
- 接口不能被实例化
- 接口中所有的方法是 public 和 abstrac 方法,接口中抽象方法,可以不用 abstract 修饰
- 普通类实现接口,就必须将该接口的所有方法实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类同时可以实现多个接口
- 接口中的属性,只能是 final 的,而且是 public static final 修饰符
- 接口中属性的访问形式:接口名.属性名
- 一个接口不能继承其它的类,但是可以继承多个别的接口
- 接口的修饰符 只能是 public 和 默认,这点和类的修饰符是一样的
-
实现接口VS继承类:
- 继承的价值主要在于:解决代码的复用性和可维护性
- 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法
- 接口比继承更加灵活,继承是满足 is -a的关系,而接口只需满足 like-a的关系
- 接口在一定程度上实现代码解耦
-
接口类型数组:
public class InterfacePolyArr { public static void main(String[] args) { //多态数组 -> 接口类型数组 Usb[] usbs = new Usb[2]; usbs[0] = new Phone_(); usbs[1] = new Camera_(); /* 给Usb数组中,存放 Phone 和 相机对象,Phone类还有一个特有的方法call(), 请遍历Usb数组,如果是Phone对象,除了调用Usb 接口定义的方法外, 还需要调用Phone 特有方法 call */ for(int i = 0; i < usbs.length; i++) { usbs[i].work();//动态绑定.. //和前面一样,我们仍然需要进行类型的向下转型 if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_ ((Phone_) usbs[i]).call(); } } } } interface Usb{ void work(); } class Phone_ implements Usb { public void call() { System.out.println("手机可以打电话..."); } @Override public void work() { System.out.println("手机工作中..."); } } class Camera_ implements Usb { @Override public void work() { System.out.println("相机工作中..."); } }
8.8内部类⭐️
-
基本介绍:
- 一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)
- 内部类最大的特点就是可以直接访问私有属性,井且可以体现类与类之间的包含关系
-
基本语法:
class Outer{//外部类 class Inner{//内部类 } } class Other{//外部其他类 }
-
内部类分类:
- 定义在外部类局部位置上:1局部类内部(有类名) 2.匿名局部类(没有类名⭐️)
- 定义在外部类的成员位置上:1.成员内部类(无static修饰) 2.静态内部类(static修饰)
8.8.1 局部内部类
-
基本介绍:
局部内部类是定义在外部类的局部位置,在方法中、代码块中,并且有类名
-
注意事项和细节:
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,但是可以使用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(); } }
-
8.8.2 匿名内部类⭐️⭐️
-
基本介绍:
匿名内部类是定义在外部类的局部位置,比如方法中,并目没有类名,同时还是一个对象
-
基本语法:
new 类或接口(参数列表){ 类体; };
-
底层:
- 本质: IA tiger = class Xxxx$1 class Xxxx$1 = new IA
- 接口的底层:class Xxxx$1 implement IA{@Overide}
- 本质: Father father = class Xxxx$2 class Xxxx$2 = new Father
- 类的底层:class Xxxx$2 extends Father{@Overide}
-
注意事项和细节:
-
匿名内部类既是一个类的定义同时本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
-
可以直接访问外部类的所有成员,包含私有的
-
不能添加访问修饰符,因为它的地位就是一个局部变量
-
作用域:仅仅在定义它的方法或代码块中
-
匿名内部类方位外部类成员:直接访问
-
外部其他类访问匿名内部类:不能访问
-
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
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.需求是 Tiger/Dog 类只是使用一次,后面再不使用 //4. 可以使用匿名内部类来简化开发 //5. tiger的编译类型 ? IA //6. tiger的运行类型 ? 就是匿名内部类 Outer04$1 /* 我们看底层 会分配 类名 Outer04$1 class Outer04$1 implements IA { @Override public void cry() { System.out.println("老虎叫唤..."); } } */ //7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址 // 返回给 tiger //8. 匿名内部类使用一次,就不能再使用 IA tiger = new IA() { @Override public void cry() { System.out.println("老虎叫唤..."); } }; System.out.println("tiger的运行类型=" + tiger.getClass()); tiger.cry(); // IA tiger = new Tiger(); // tiger.cry(); //演示基于类的匿名内部类 //分析 //1. father编译类型 Father //2. father运行类型 Outer04$2 //3. 底层会创建匿名内部类 /* class Outer04$2 extends Father{ @Override public void test() { System.out.println("匿名内部类重写了test方法"); } } */ //4. 同时也直接返回了 匿名内部类 Outer04$2的对象 //5. 注意("jack") 参数列表会传递给 构造器 Father father = new Father("jack"){ @Override public void test() { System.out.println("匿名内部类重写了test方法"); } }; System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2 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) {//构造器 System.out.println("接收到name=" + name); } public void test() {//方法 } } abstract class Animal { //抽象类 abstract void eat(); }
public class LocalInnerClassDetail { public static void main(String[] args) { Outer03 outer03 = new Outer03(); outer03.f1(); } } class Outer03{ private int n1 = 99; public void f1(){ Person person = new Person(){ @Override public void hi() { System.out.println("匿名内部类重写hi方法"); } }; person.hi(); new Person(){ @Override public void hi() { System.out.println("匿名内部类重写ok方法"); } @Override public void ok(String str) { super.ok(str); } }.ok("jack"); } } class Person{ public void hi(){ System.out.println("Person hi()"); } public void ok(String str){ System.out.println("Person ok()"+str); } }
public class InnerClassExercise02 { public static void main(String[] args) { CellPhone cellPhone = new CellPhone(); cellPhone.alarmclock(new Bell(){ public void ring(){ @Override System.out.println("懒猪起床"); } }); } } interface Bell{ void ring(); } class CellPhone{ public void alarmClock(Bell bell){ bell.ring(); } }
-
8.8.3 成员内部类
-
基本介绍:
成员内部类是定义在外部类的成员位置,并且没有static修饰
-
注意事项和细节:
-
可以直接访问外部类的所有成员,包含私有的
-
可以添加任意访问修饰符
-
作用域:为整个外部类类体
-
成员内部类访问外部类:直接访问
-
外部类访问成员内部类;创建对象,再访问
-
外部其他类访问成员内部类:
-
外部类.内部类 引用名 = 外部对象.new 内部类();
-
外部类.内部类 引用名 = 外部对象.get();
-
-
如果外部类和成员内部类的成员重名时,成员内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.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); } }
-
8.8.4 静态内部类
-
基本介绍:
静态内部类是定义在外部类的成员位置,并且有static修饰
-
注意事项和细节:
-
可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
-
可以添加任意访问修饰符
-
作用域:整个外部类类体
-
静态内部类访问外部类:直接访问
-
外部类访问静态内部类:创建对象,再访问
-
外部其他类访问静态内部类:
-
外部类.内部类 引用名 = new 外部类.内部类();
-
外部类.内部类 引用名 = 外部对象.get();
-
外部类.内部类 引用名 = 外部类.get();
-
-
如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
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(); } }
-
8.9 类加载顺序⭐️⭐️
-
类什么时候被加载:
- 创建对象实例时
- 创建子类对象实例时,父类也会被加载
- 使用类的静态成员时
-
创建一个对象时,在一个类的调用顺序:
-
调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级样,如果有多个静态代码块和多个静
态变量初始化,则按他们定义的顺序调)
-
调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,若果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
-
调用构造方法
-
-
创建子类时顺序:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类构造方法