12、面向对象(高级)
一、类变量和类方法
独立于对象之外的变量和方法
1、类变量(静态变量)
(1)为什么需要静态变量?
在实际开发场景中,处理问题时,会需要两个类在同一内存区域中共享一个数据,或共用一个方法,此时就需要使用到静态变量和静态方法
(2)什么是静态变量?
类变量也叫静态变量,静态属性,为该类所有对象共享的变量
- 语法:
访问修饰符 static 数据类型 变量名; //(推荐)
static 访问修饰符 数据类型 变量名;
(3)怎么使用静态变量?
- 访问
类名.类变量名;(推荐)
对象名.类变量名;
(4)使用静态变量时需要注意的问题
- 使用时机:需要让某个类所有对象共享一个属性时,使用静态变量
- 类变量与实例变量的区别
- 类变量为该类所有对象共享
- 实例变量每个对象各自独享
- 加上static的变量称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 访问时需要满足访问权限和范围这一限制
- 实例变量不能通过类名访问
- 类变量在类加载时就创建了,即使无实例对象也能访问
- 类变量的生命周期随着类的加载而开始,随着类的消亡而结束
2、类方法(静态方法)
(1)什么是静态方法?
- 语法
访问修饰符 static 数据返回类型 方法名() {}
(推荐)static 访问修饰符 数据返回类型 方法名() {}
(2)怎么使用静态方法?
在满足访问修饰符的前提下
- 访问
类名.类方法名
对象.类方法名
(3)使用静态方法时需要注意的问题
- 类方法和普通方法随类的加载而加载,将结构信息存储在方法区
- 类方法中无this的参数
- 普通方法隐含this的参数
- 类方法可以通过类名调用,也可以通过对象调用
- 普通方法和对象有关,需要对象调用
- 类方法中不允许使用和对象有关的关键字(
this
,super
) - 类方法只能访问类变量和类方法(静态方法只能访问静态成员)
- 普通方法既可以访问静态成员,又可以访问非静态成员
- 静态属性,方法可以别继承,但不能被重写
二、main方法语法解析
public static void main(String[] args) {}
(1)理解main方法
- main方法由Java虚拟机调用
- JVM需要调用类的main方法,则该方法的访问权限必须为
public
- JVM在执行main方法时,不必创建对象,由类名直接调用,则该方法是
static
- 该方法接受String类型数组参数,该数组保存执行Java命令时传递给所运行的类的参数
String[] args
的意义:传递参数——在控制台输入:java 文件名 参数一 参数一 ...
(2)说明
- 在main方法中,可以直接调用main方法所在的类的静态方法或属性
- 但不能访问该类中非静态成员,必须实例化对象才能访问
三、代码块{}
1、什么是代码块?
(1)代码块
代码块属于类中的成员(类的一部分)
类似于方法,将逻辑语句封装在方法体中,通过{}
包围起来
但是与方法不同,代码块没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类,或创建对象时隐式调用
(2)语法
修饰符 {
代码;
};
- 修饰符可选无或static
- 逻辑语句中可以为任何逻辑语句(输入,输出,方法调用,循环,判断等)
;
可以省略
(3)三种代码块
- 普通类中方法的方法体
- 构造代码块:创建对象时被调用,优先于构造方法被执行(初始化一个类的所有构造器的共有特征)
- 静态代码块:只执行一次,用来初始化静态成员变量(随着类的加载而执行)
2、使用细节
(1)静态代码块随着类的加载而执行,且只执行一次
(2)类什么时候被加载
- 创建对象实例时
- 创建子类对象实例时,父类也会别加载
- 使用类的静态成员时,类被加载,静态代码块随着类的加载而执行
(3)构造代码块在创建实例时被隐式地调用,创建一次,调用一次
(4)代码块在创建一个对象时,在一个类中的调用顺序是
- 调用静态代码块,和静态属性的初始化(二者优先级一样,按顺序执行)
- 调用构造代码块,和普通属性的初始化(二者优先级一样,按顺序执行)
- 调用构造方法
(5)构造器前隐含了super()
构造器,和调用构造代码块,即父类构造器优先于调用本类构造代码块
(6)创建一个子类对象时,他们的静态代码块,静态属性初始化,构造代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性的初始化
- 子类的静态代码块和静态属性的初始化
- 父类的构造代码块和普通属性的初始化
- 父类的构造器
- 子类的构造代码块和普通属性的初始化
- 子类的构造器
(7)静态代码块只能直接调用静态成员,构造代码块可以调用任意成员
3、代码块的好处
(1)构造器的补充机制,相当于另外一种形式的构造器,可以做初始化的操作
(2)如果多个构造器中都有重复语句,可以抽取到代码块中,提高代码的重用性
四、单例设计模式(静态方法和属性的经典使用)
(1)什么是设计模式?
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式
(2)什么是单例模式?
所谓的单例模式,即单个实例,指采取一定的方法保证在整个的软件系统中只能存在一个实例,并且该类只提供一个取得其对象的方法
- 饿汉式
- 懒汉式
(3)饿汉式
- 构造器私有化——》防止用户新建实例
- 类的内部创建对象(静态属性)
- 向外暴露一个静态的公共方法——》
getInstance()
public class SingleTon01 {
//通过类调用静态方法获取对象
GirlFriend instance = GirlFriend.getInstance();
}
class GirlFriend {
private String name;
//2、在类的内部创建对象
//为了能在静态方法中使用,需要将其修饰为静态方法
private static GirlFriend gf = new GirlFriend("小花花");
//保证只有一个实例对象
//1、构造器私有化
private GirlFriend(String name) {
this.name = name;
}
//3、提供暴露的静态公共方法,返回gf对象
public static GirlFriend getInstance() {
return gf;
}
}
(4)懒汉式
- 构造器私有化
- 类的内部类创建对象的引用(不直接指向对象,不直接实例化)
- 提供静态公共方法,对引用进行判断,为空则实例化,return对象
public class SingleTon02 {
}
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;
}
}
(5)比较
- 二者主要区别在于创建对象的时机不同,饿汉式类加载即创建,懒汉式使用才创建
- 饿汉式五线程安全问题,懒汉式有线程安全问题
- 饿汉式存在资源浪费的可能
五、final关键字
可以用来修饰类,属性,方法和局部变量
- 基本数据类型:值不变
- 引用数据类型:对象不变
- 方法:不能重写
- 类:不能继承
1、使用场景
(1)当不希望类被继承时,用final修饰——修饰类
(2)当不希望父类的某个方法被子类重写时,用final——修饰方法
(3)当不希望类的某个属性被修改时,用final——修饰属性
(4)不希望某个局部变量被修改——修饰局部变量
2、使用细节
(1)final修饰的属性又叫做常量,一般用XX_XX_XX来命名
(2)final修i是的属性在定义时,必须赋初始值,且不能修改
-
赋值的位置
- 定义时:
public final double TAX_RATE = 0.08;
- 在构造器中可以给常量赋值
- 在代码块中给常量赋值
类的成员变量,局部变量在使用前初始化即可
- 定义时:
(3)如果final修饰的属性为静态的,则初始化位置只能在如下所示位置
-
位置
- 定义时
- 静态代码块中
类加载——》静态代码块执行
(4)final修饰类不能继承,但可以实例化对象
(5)如果不是final类,含有final方法,则该方法不能重写,但可以被继承
(6)final类中所有的方法被隐式设置为final方法
(7)final不能修饰构造方法
(8)final和static往往搭配使用,效率更高,不会导致类加载(底层做了优化)
(9)包装类(Integer,Double,Float,Boolean)都是final类,String也是final类
六、抽象类(abstract)
1、为什么需要抽象类?
父类方法的不确定性
当父类的某些方法需要声明,但又不确定该如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
- 抽象方法:即没有实现的方法
- 没有实现:没有方法体
- 抽象类:当一个方法为抽象方法时,类为抽象类
2、抽象类的介绍
抽象类除了继承毫无意义
(1)用abstract关键字来修饰一个类时,类为抽象类
语法:访问修饰符 abstract class 类名 {}
(2)抽象方法
语法:访问修饰符 abstract 返回类型 方法名(形参列表);
(无方法体)
(3)抽象类的价值在于设计,是设计者设计好后,让子类实现抽象类
(4)抽象类为考官常考知识点,子啊框架和设计模式中涉及较多
3、使用细节
(1)抽象类不能实例化
(2)抽象类不一定要有抽象方法,而且可以有实现方法
(3)有抽象方法一定是抽象类
(4)abstract只能修饰类和方法
(5)抽象类仍然是类,可以有任意类可以有的成员
(6)抽象方法不能有方法体
(7)如果一个类继承了抽象类,则必须实现(重写)抽象类的所有抽象方法,除非它也声明为abstract类
(8)抽象方法不能使用private,final和static来修饰,因为与重写向违背
- private:私有,子类无法继承父类特有的成员
- final:final方法子类不能继承
- static:静态成员只与类有关,可以继承不能重写
七、模板设计模式——抽象类的最佳实践
1、基本介绍
抽象类体现的就是一种模板模式的设计,抽象类作为子类的通用模板
子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为
2、作用
(1)当内部功能一部分实现是确定的,一部分实现是不确定的,这时可以把不确定的部分暴露出去,由子类实现
(2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式
public class AbstractExercise01 {
public static void main(String[] args) {
//创建员工和经理
CommonEmployee commonEmployee = new CommonEmployee("jack", 51, 2000);
Manager manager = new Manager("tony", 52, 20000, 100000);
commonEmployee.work();
manager.work();
}
}
//抽象类
abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public abstract void work();
}
class Manager extends Employee {
private double bonus;//奖金
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理" + this.getName() + "正在工作中...");
}
}
class CommonEmployee extends Employee {
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("员工" + this.getName() + "正在工作中...");
}
}
八、接口(interface)
接口就是一个标准
1、为什么需要接口?
(1)接口被用来描述一种抽象(用来实现多继承)
(2)接口被用来实现抽象,抽象类也被用来实现抽象,为什么一定要用接口,二者之间的区别是什么?
- 抽象类内部可能含有非final的变量
- 但是在接口中存在的变量一定是final
- final即不可改变,不可改变即为一个标准
(3)为不同类顺利交互提供标准
2、什么是接口?
接口就是给出一些没有实现的方法,封装到一起,在某个类要使用的时候,根据具体情况,实现方法
- 语法:
//定义接口
interface 接口名 {
//属性
//方法
}
//实现
class 类名 implements 接口名 {
//属性
//方法
//必须实现的接口的抽象方法
}
- 接口中的方法:
- 抽象方法
- 默认实现方法
- 静态方法
3、应用场景
指定标准,接口是需求的实际体现
4、注意事项
(1)接口不能被实例化
(2)接口中的所有方法是public方法,接口中抽象方法可以不用abstract修饰,因为interface中方法默认为public abstract修饰
(3)一个普通类实现接口,就必须将该接口的所有方法都实现
(4)除非声明为抽象类,上(3)可不用全部实现
(5)一个类可以实现多个接口
(6)接口中的属性 ,只能是final
,而且是public static final
修饰
(7)接口属性的访问:接口名.属性;
(8)接口不能继承类,但可以继承多个别的接口
- 类继承类
- 类实现接口
- 接口继承接口
(9)接口修饰符和类一样,只能是public和默认的
5、接口与继承
(1)解决问题不同
- 继承:解决代码复用性和可维护性
- 接口:设计,设计好各种方法的规范,由其他类实现
(2)接口比继承更灵活
- 继承:满足
is-a
关系,子类是一个父类 - 接口:满足
like-a
,类像一个接口
(3)接口在一定程度上实现代码解耦
接口规范性+动态绑定
6、接口的多态性
(1)参数的多态性
接口引用可以指向实现了接口的类的对象
(2)多态数组
public class Arr {
public static void main(String[] args) {
//多态数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone();
usbs[1] = new Camera();
//调用
for(int i = 0; i < usbs.length; i++) {
usbs[i].work;//动态绑定
if (usbs[i] instenceof Phone) {
((Phone)usbs[i]).call();//向下转换:前提是引用指向的对象本来就是Phone类
}
}
}
}
//接口
interface Usb {
void work();
}
class Phone implements Usb {
public void call() {
"手机打电话".sout;//输出
}
//实现
public void work() {
"手机工作中".sout;
}
}
class Camera implements Usb {
public void work() {
"相机工作中".sout;
}
}
(3)接口的多态传递
interface IH{}
//IG 继承 IH
interface IG extends IH {}
//Teacher类实现IG接口的同时也实现了IH接口
class Teacher implements IG {}
//主方法简写
main {
IG ig = new Teacher();//一个IG接口的引用指向了一个Teacher类的实例
IH ih = new Teacher();//一个IH接口的引用指向了一个Teacher类的实例
}
九、内部类
1、为什么需要内部类?
每个内部类都能独立地实现一个接口,无论外部类是否实现了接口,对内部类而言都不影响,从而结局了Java多继承的问题
2、什么是内部类?
一个类的内部完整地嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类为外部类,是类的第五大成员(属性,方法,构造器,代码块,内部类)
最大的特点:可以直接访问外部类的私有属性,并且可以体现类之间的层级关系
3、语法
class 外部类 {
class 内部类 {
}
}
class 外部其他类 {
}
4、内部类的分类
-
定义在外部类成员的位置上的内部类,按静态非静态划分为
-
成员内部类
-
静态内部类
-
-
定义在外部类成员的局部位置上的内部类,按有无类名区分为
- 局部内部类
- 匿名内部类
5、成员内部类
外部类的成员位置,且非static
(1)可以直接访问外部类的所有成员,包含私有的
(2)地位为成员,则可以添加任意访问修饰符
(3)作用域:和外部其他类一样,整个类体
(4)成员内部类访问外部类:直接访问
(5)外部类访问成员内部类成员:在外部类里建立成员内部类对象,再调用
(6)外部其他类访问成员内部类成员
外部类.内部类 引用 = 外部类对象.new 内部类(实参列表);
- 外部类编写一个方法,返回一个内部类的对象实例
(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;
外部类名.this
:表示指向外部类的对象
6、静态内部类
外部类的成员位置,为静态
(1)可以访问外部类所有静态成员,不能访问非静态成员
(2)可以添加任意修饰符
(3)作用域:整个类体
(4)静态内部类直接访问外部类成员
(5)外部类访问静态内部类成员:实例化静态内部类一个对象,再调用、
静态内部类只是说明该类属于外部类,而不属于外部类实例化的对象,而静态内部类的非静态成员,依然和静态内部类的对象相联系,所以需要实例化静态内部类对象后再调用目标成员
(6)外部其他类访问静态内部类
-
外部类.静态内部类 引用 = new 外部类.静态内部类(); 引用.成员;
静态成员可以通过类名来调用,静态内部类为静态成员,ps:静态内部类的成员不一定为静态
- 在外部类编写一个方法,返回静态内部类实例对象
(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;
7、局部内部类
通常在方法中,有类名
(1)可以直接访问外部类的所有成员,包含私有的
(2)地位为一个局部变量,不能使用权限修饰符,但可以使用final关键字
(3)作用域:仅在定义它的方法或代码块中
(4)局部内部类访问外部类成员:直接访问
(5)外部类访问局部内部类:满足作用域的条件下(即在方法中),创建对象再访问
(6)外部其他类无法访问局部内部类:因为局部内部类为一个成员变量
(7)外部类和内部类成员重名时,就近原则,若要访问外部类——外部类名.this.成员;
8、匿名内部类
匿名的局部内部类,不是没名,而是系统自动分配类名
(1)语法
new 类或接口(参数列表) {
类体;
};
//底层运行逻辑
class 外部类名$02 implements 类或接口 {//实现了类接口或继承了类
类体;
}
new 外部类名$02();//实例化一个匿名内部类的对象
//例子
public class AnonymousInnerClass {//外部其他类
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer {//外部类
private int n1 = 10;//属性
public void method() {//外部类的方法中建立内部类
/**
* 1、需求:使用A接口,并创建对象
* 2、传统方式:创建一个类重写方法,实现接口,并创建对象
* 3、若tiger类只使用一次,后面不再使用
* 4、解决方案:使用匿名内部类,来简化开发
* */
//基于接口的匿名内部类
/**
* 匿名内部类基于接口的解析
* 1、tiger的编译类型为:IA接口类型
* 2、tiger的运行类型为:匿名内部类,匿名内部类并非没有名字,只是由系统分配类名,底层如下
* -IA() {类体};:创建了一个实现了接口的匿名内部类,注意为一个语句,需要分号“ ; ”
* class Outer$01 implements IA {
* 类体
* };
* new IA() {}; :创建了一个匿名内部类的对象
* new Outer$01();
* IA tiger = new IA() {};
* 将实例化的一个实现了A接口的匿名内部类的对象的地址传递给tiger
*3、匿名内部类使用一次,就不能再使用了,但是tiger可以重复使用
* */
IA tiger = new IA() {//基于接口的匿名内部类
@Override
public void cay() {
System.out.println("老虎嗷嗷叫");
}
};
//在外部类中,在内部类作用域即方法中,调用内部类的成员——创建一个指向内部类对象的引用,用对象来调用方法
tiger.cay();
// Tiger tiger = new Tiger();
// tiger.cay();
//基于类的匿名内部类
/**
* 匿名内部类基于类的解析
* 1、father的编译类型:Father
* 2、father的运行类型:Outer$2 father对象的运行类型:class com.zyhstu.innerclass.Outer$2
* -Father("jack") {类体};:创建了一个继承了Father父类的Outer$2子类
* class Outer$2 extends Father {
* 类体;
* };
* -new Father("jack") {类体};:创建一个匿名内部类Outer$2的对象,并将jack自动传给Father的构造器
* new Outer$2() {类体};
* -Father father = new Father("jack") {};
* 实例化一个继承了Father父类的Outer子类的对象,并将地址传递给一个Father的引用
* */
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test()方法");
}
};
father.test();
System.out.println("father对象的运行类型:" + father.getClass());
//基于抽象类的匿名内部类
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("小狗吃骨头");
}
};
animal.eat();
}
}
interface IA {
public void cay();
}
//class Tiger implements IA {
// @Override
// public void cay() {
// System.out.println("老虎嗷嗷叫");
// }
//}
//
//class Dog implements IA {
// @Override
// public void cay() {
// System.out.println("小狗汪汪叫");
// }
//}
class Father {//外部其他类
public Father(String name) {//构造器
}
public void test() {//方法
}
}
//抽象类
abstract class Animal {
abstract void eat();
}
(2)匿名内部类的使用
-
new 类或接口(实参列表) { 类体; }.方法(方法实参);
-
类名 引用 = new 类或接口(实参列表) { 类体; } 引用.方法(方法实参);
(3)注意事项
- 可以直接访问外部类的所有成员,包含私有的
- 地位为一个局部变量,不能使用权限修饰符,但可以使用final关键字
- 作用域:仅在定义它的方法或代码块中
- 匿名内部类访问外部类成员:直接访问
- 外部其他类不能访问匿名内部类:局部变量
- 外部类和内部类成员重名时,就近原则,若要访问外部类——
外部类名.this.成员;