面向对象编程(高级)
笔记目录:(https://www.cnblogs.com/wenjie2000/p/16378441.html)
类变量和类方法(static)
类变量
类变量-提出问题
提出问题的主要目的就是让大家思考解决之道,从而引出我要讲的知识点.说:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?,编写程序解决。
传统的方法来解决
使用我们现有的技术来解决这个问题,大家看看如何?
✔思路
- 在main方法中定义一个变量count
- 当一个小孩加入游戏后count++,最后个count 就记录有多少小孩玩游戏
- public class ChildGame {
- public static void main(String[] args) {
- //定义一个变量count,统计有多少小孩加入了游戏
- int count = 0;
- Child child1 = new Child("白精");
- child1.join();
- count++;
- Child child2 = new Child("老白");
- child2.join();
- count++;
-
- Child child3 = new Child("老白");
- child3.join();
- child3.count++;
-
- System.out.println("共有" + count + "小孩加入了游戏...");
- }
- }
- class Child {//类
- private String name;
- public Child(String name) {
- this.name = name;
- }
- public void join() {
- System.out.println(name + "加入了游戏..");
- }
- }
复制代码 ✔问题分析:
- count是一个独立于对象,很尴尬
- 以后我们访问count很麻烦,没有使用到OOP
- 3.因此,我们引出类变量/静态变量
类变量快速入门
思考:如果,设计一个int count表示总人数,我们在创建一个小孩时,就把count加1,并且 count是所有对象共享的就ok了!,我们使用类变量来解决- public class ChildGame {
- public static void main(String[] args) {
- Child child1 = new Child("白精");
- child1.join();
- child1.count++;
- Child child2 = new Child("老白");
- child2.join();
- child2.count++;
-
- Child child3 = new Child("老白");
- child3.join();
- child3.count++;
-
- //......
- System.out.println("共有" + child1.count + "小孩加入了游戏...");
- System.out.println(child1.count);//3
- System.out.println(child2.count);//3
- System.out.println(child3.count);//3
- System.out.println(Child.count);//3
- }
- }
- class Child {//类
- private String name;
- //定义一个变量 count,是一个类变量(静态变量)static静态
- //该变量最大的特点就是会被Child类的所有的对象实例共享
- public static int count = 0;
- public Child(String name) {
- this.name = name;
- }
- public void join() {
- System.out.println(name + "加入了游戏..");
- }
- }
复制代码 其中child1,child2以及child3中的count指向相同空间,被共享,为同一值
jdk8以及之前,count(也就是静态域)在方法区中。jdk8之后,jdk存放在堆中
有些书说在方法区... jdk版本有关系,记住一点: static变量是对象共享
不管static变量在哪里,共识:
- static变量是同一个类所有对象共享
- static类变量,在类加载的时候就生成了.
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。
如何定义类变量
定义语法:
访问修饰符 static 数据类型 变量名;[推荐]
static 访问修饰符 数据类型 变量名;- class A{
- public static string name = "abc";
- static public int totalNum = 100;
- }
复制代码 如何访问类变量
类名.类变量名
或者对象名.类变量名【静态变量的访问健饰符的访问权限和范围和普通属性是一样的。】
推荐使用:类名.类变量名;- public class Test {
- public static void main(String[] args) {
- //类名.类变量名
- // 说明:类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
- System.out.println(A.name);
- A a = new A();
- System.out.println("a.name=" +a.name);
- }
- }
- class A {
- //类变量
- public static String name = "韩顺平";
- }
复制代码 类变量使用注意事项和细节讨论
- 什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student (name, fee)
- 类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
- 加上static称为类变量或静态变量,否则称为 实例变量/普通变量/非静态变量
- 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐我们使用 类名.类变量名 方式访问。【前提是 满足访问修饰符的访问权限和范围】
- 实例变量不能通过类名.类变量名方式访问。
- 类变量是在类加载时就初始化了,也就是说,即使你没有助建家,只安尖加郓,就可以使用类变量了。
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法
类方法基本介绍
类方法也叫静态方法。
形式如下:
访问修饰符 static 数据返回类型 方法名(){ }【推荐】
static 访问修饰符 数据返回类型 方法名(){ }
类方法的调用:
使用方式:类名.类方法名 或者 对象名.类方法名【前提是满足访问修饰符的访问权限和范围】
类方法应用案例
请大家看一个静态方式小案例。(统计学费总和)- public class Test {
- public static void main(String[] args) {
- //创建2个学生对象,叫学费
- Stu tom = new Stu("tom");
- tom.payFee(100);
- Stu mary = new Stu("mary");
- mary.payFee(200);
-
- //输出当前收到的总学费
- Stu.showFee();//300
- }
- }
- class Stu {
- private String name;//普通成员
- // 定义一个静态变量,来累积学生的学费
- private static double fee = 0;
- public Stu(String name) {
- this.name = name;
- }
- //说明
- //1.当方法使用了static修饰后,该方法就是静态方法
- //2.静态方法就可以访问静态属性/变量
- public static void payFee(double fee) {
- Stu.fee += fee;//累积到
-
- }
- public static void showFee() {
- System.out.println("总学费有:" + Stu.fee);
- }
- }
复制代码 类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。(不需要额外创建对象)
比如:工具类中的方法utils
Math类、Arrays类、Collections集合类看下源码:
小结
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等..
举例- public class Test {
- public static void main(String[] args) {
- System.out.println(MyTools.calSum(10,30));
- }
- }
- class MyTools {
- //开发自己的工具类时,可以将方法做成静态的,方便调用class MyTools {
- //求出两个数的和
- public static double calSum(double n1, double n2) {
- return n1 + n2;
- }
- }
复制代码 类方法使用注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this的参数
普通方法中隐含着this的参数
- 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调
用。
- 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。[举例]
- 类方法(静态方法)中只能访问静态变量或静态方法。【如何理解】
- 普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)。
小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
理解main方法语法
●深入理解main方法
解释main方法的形式:public static void main(String[] args){}
- main方法时虚拟机调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数,案例演示,接收参数.
- java 执行的程序 参数1 参数2 参数3
特别提示:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
代码块
●基本介绍
代码化块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
●基本语法
[修饰符]{
代码
};
注意:
- 修饰符可选,要写的话,也只能写static
- 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块。
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ;号可以写上,也可以省略。
实例- package com.hspedu.p386;
- public class codeBlock01 {
- public static void main(String[] args) {
- Movie movie = new Movie("你好,李焕英");
- }
- }
- class Movie {
- private String name;
- private double price;
- private String director;
- //3个构造器-》重载
- //老韩解读
- //(1)下面的三个构造器都有相同的语句
- //(2)这样代码看起来比较冗余
- //(3)这时我们可以把相同的语句,放入到一个代码块中,即可
- //(4)这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
- //(5)代码块调用的顺序优先于构造器。。
- {
- System.out.println("电影屏幕打开...");
- System.out.println("广告开始...");
- System.out.println("电影正是开始...");
- }
- public Movie(String name) {
- System.out.println("Hovie(Strihg name)被调用。.。");
- this.name = name;
- }
- public Movie(String name, double price) {
- this.name = name;
- this.price = price;
- }
- public Movie(String name, double price, String director) {
- this.name = name;
- this.price = price;
- this.director = director;
- }
- }
复制代码 代码块使用注意事项和细节讨论
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
- 类什么时候被加载
①创建对象实例时(new)
②创建子类对象实例,父类也会被加载
③使用类的静态成员时(静态属性,静态方法)
案例演示: A类 extends B类 的静态块
- 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。
小结:
- static代码块是类加载时,执行,只会执行一次
- 普通代码块是在创建对象时调用的,创建一次,调用一次
- 类加载的3种情况,需要记住.
- public class Test {
- public static void main(String[] args) {
- // new A();
- // new A();
- //
- // System.out.println("------------------");
- // new B();
- // System.out.println("------------------");
- // System.out.println(A.a);
- System.out.println(B.b);
- }
- }
- class A {
- public static int a=10;
- static {//只在加载类时运行
- System.out.println("111111");
- }
- {//普通代码块,在new对象时,被调用,而且是每创建一个对象,就调用一次
- //可以这样简单的,理解普通代码块是构造器的补充
- System.out.println("普通");
- }
- }
- class B extends A{
- static {
- System.out.println("2222222");
- }
- }
复制代码
- 创建一个对象时,在一个类调用顺序是:(重点,难点)∶
①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③调用构造方法。
- 构造器的最前面其实隐含了super()和调用普通代码块,新写一个类,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
- class A2 {
- public void A() { //构造器
- //这里有隐藏的执行要求
- //(1) super():这个知识点,在前面讲解继承的时候,老师说
- // (2)调用普通代码块的
- System.out.println("ok");
- }
- }
复制代码 - 我们看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④父类的构造方法
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥子类的构造方法//面试题
- public class Test {
- public static void main(String[] args) {
- //老师说明
- //(1)进行类的加载
- //1.1 先加载父类A02 1.2 再加载Bo2//(2)创建对象
- new B02();//对象
- }
- }
- class A02 {//父类
- private static int n1 = getVal01();
- static {
- System.out.println("A02的一个静态代码块..");//2
- }
- {
- System.out.println("A02的第一个普通代码块..");//5
- }
- public int n3 = getVal02();
- public static int getVal01() {
- System.out.println("getVal01");//1
- return 10;
- }
- public int getVal02() {
- System.out.println("getVal02");//6
- return 10;
- }
- public A02() {
- System.out.println("A02的构造器");//7
- }
- }
- class B02 extends A02 { //
- private static int n3 = getVal03();
- static {
- System.out.println("BO2的一个静态代码块..");//4
- }
- public int n5 = getVal04();
- {
- System.out.println("B02的第一个普通代码块..");//9
- }
- public static int getVal03() {
- System.out.println("getVal03");//3
- return 10;
- }
- public int getVal04() {
- System.out.println("getVal04");//8
- return 10;
- }
- public B02() {
- System.out.println("BO2的构造器");//10
- }
- }
复制代码
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
单例设计模式
什么是单例模式
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式: 1)饿汉式 2)懒汉式
单例模式应用实例
演示饿汉式和懒汉式单例模式的实现。步骤如下:
- 构造器私有化=》防止直接new
- 类的内部创建对象
- 向外暴露一个静态的公共方法。getInstance
- 代码实现
饿汉式- public class Test {
- public static void main(String[] args) {
- GirlFriend instance = GirlFriend.getInstance();
- System.out.println(instance);
- GirlFriend instance2 = GirlFriend.getInstance();
- System.out.println(instance2);
- System.out.println(instance == instance2);//T
- }
- }
- class GirlFriend {
- private String name;
- //为了能够在静态方法中,返回gf对象,需要将其修饰为static
- private static GirlFriend gf = new GirlFriend("小红红");
- //如何保障我们只能创建一个 GirlFriend对象
- //步骤
- //步骤[单例模式-饿汉式]
- //1。将构造器私有化
- // 2.在类的内部直接创建
- //3.提供一个公共的static方法,返回 gf对象
- private GirlFriend(String name) {
- this.name = name;
- }
- public static GirlFriend getInstance() {
- return gf;
- }
- }
复制代码 懒汉式- package com.hspedu.test;
- public class Test {
- public static void main(String[] args) {
- //new Cat("大黄");
- Cat instance = Cat.getInstance();
- System.out.println(instance);
- }
- }
- class Cat {
- private String name;
- private static Cat cat;//步骤
- //1.仍然构造器私有化
- //2.定义一个static属性对象
- //3.提供一个public的static方法,可以返回一个Cat对象
- private Cat(String name) {
- this.name = name;
- }
- public static Cat getInstance() {
- if (cat == null) {//如果没有创建cat封象
- cat = new Cat("小可爱");
- }
- return cat;
- }
- }
复制代码 饿汉式VS懒汉式
- 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善)
- 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式。
final关键字
●基本介绍
final中文意思:最后的,最终的.
final 可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求,就会使用到final:
- 当不希望类被继承时,可以用final修饰.
- final class A{ }
- class B extends A {}//会报错
复制代码 - 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。【案例演示:访问修饰符 final 返回类型方法名】
- class c {
- //如果我们要求hi不能被子类重写
- //可以使用final修饰hi方法
- public final void hi() {}
- }
- class D extends C {
- @0verride
- public void hi {//报错
- System.out.println("重写了C类的hi方法..");
- }
- }
复制代码 - 当不希望类的的某个属性的值被修改,可以用final修饰.【案例演示: public final double TAX_RATE=0.08】
- 当不希望某个局部变量被修改,可以使用final修饰【案例演示: final double TAX_RATE=0.08)
- class F {
- public void cry() {
- //这时,NUM也称为局部常量
- final double NUM = 0.01;
- NUM= 0.9;//报错
- System.out.println("NUM=" + NUM);
- }
- }
复制代码 final使用注意事项和细节讨论
- final修饰的属性又叫常量,一般用XX_XX_XX 来命名
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
①定义时:如public final double TAX_RATE=0.08;
②在构造器中
③在代码块中。
- 如果final修饰的属性是静态的,则初始化的位置只能是
①定义时
②在静态代码块赋值,不能在构造器中赋值。
- final类不能继承,但是可以实例化对象。
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。(多此一举)
- final不能修饰构造方法(即构造器)
- final和static往往搭配使用,效率更高,底层编译器做了优化处理。
- 包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
抽象类
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类- abstract class Animal {
- private String name;
- public Animal(String name) {
- this.name = name;
- }
- //思考:这里eat这里你实现了,其实没有什么意义//即:父类方法不确定性的问题
- //===>考虑将该方法设计为抽象(abstract)方法//===>所谓抽象方法就是没有实现的方法
- //===>所谓没有实现就是指,没有方法体
- //===>当一个类中存在抽象方法时,需要将该类声明为abstract类
-
- // public void eat() {
- // System.out.println("这是一个动物,但是不知道吃什么..");
- // }
- public abstract void eat() ;
- }
复制代码 抽象类的介绍
- 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
}
- 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
- 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
抽象类使用的注意事项和细节讨论
- 抽象类不能被实例化
- public class AbstractDetail01 {
- public static void main(String[] args) {
- //抽象类,不能被实例化
- new A();//报错
- }
- }
- abstract class A {
- }
复制代码 - 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- class B {//报错
- public abstract void hi();
- }
复制代码 - abstract只能修饰类和方法,不能修饰属性和其它的。
- 抽象类可以有任意成员【抽象类还是类】,比如:非抽象方法、构造器、静态属性等等
- 抽象方法不能有主体,即不能实现.如图所示
abstract void aaa(){......}//报错,不能存在“{}”
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。[举例 A类,B类,C类]
- //如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
- abstract class E {
- public abstract void hi();
- }
- abstract class F extends E {
- }
- class G extends E {
- @Override
- public void hi() { //这里相等于G子类实现了父类E的抽象方法,所谓实现方法,就是有方法体
- }
- }
复制代码 - 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
抽象类最佳实践-模板设计模式
最佳实践
需求
- 有多个类,完成不同的任务job
- 要求统计得到各自完成任务的时间
- 请编程实现
感情的自然流露
- 先用最容易想到的方法
- 分析问题,提出使用模板设计模式
[code]public class Test { public static void main(String[] args) { new A().job(); new B().job(); }}//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类class A { public void job() { //得到开始的时间 long start = System.currentTimeMillis(); long num = 0; for (long i = 1; i |