10.1 类变量和类方法

10.1.1 类变量提出问题

提出问题的主要目的就是让大家思考解决之道,从而引出我要讲的知识点。
说:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?,编写程序解决。

10.1.2 传统的方法解决

目前,使用我们之前所学的技术来解决这个问题,主要思路如下:

  • 在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();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

因此,由以上问题,我们引出类变量/静态变量。

10.1.3 类变量的快速入门

思考: 如果,设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 加 1,并且 count 是所有对象共享的就 ok 了!,我们使用类变量来解决 ChildGame.java 改进:

public class ChildGame {public static void main(String[] args) {//定义一个局部变量 count, 统计有多少小孩加入了游戏int count = 0;Child child1 = new Child("白骨精");child1.join();//count++;child1.count++;Child child2 = new Child("狐狸精");child2.join();//count++;child2.count++;Child child3 = new Child("老鼠精");child3.join();//count++;child3.count++;//===========//类变量,可以通过类名来访问System.out.println("共有" + Child.count  + " 小孩加入了游戏...");//下面的代码输出什么?System.out.println("child1.count=" + child1.count);//3System.out.println("child2.count=" + child2.count);//3System.out.println("child3.count=" + child3.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.count=3的之类问题。为解决这类问题,我们下面进行内存分析。

10.1.4 类变量内存布局

不管static变量在哪里,static变量是对象共享,共识:

  1. static变量是同一个类所有对象共享;
  2. static类变量,在类加载(class类)的时候就生成了。

10.1.5 什么是类变量

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也能看出来。

10.1.6 如何定义类变量

定义语法:

  • 访问修饰符 static 数据类型 变量名;(推荐)
  • static 访问修饰符 数据类型 变量名;

10.1.7 如何访问类变量

类名.类变量名 ,或者 对象名.类变量名【静态变量的访问修饰符的访问权限的范围 和 普通属性是一样的】

推荐使用:类名.类变量名;

public class VisitStatic {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 = "爱摸鱼的TT~";
}

10.1.8 类变量使用注意事项和细节

  • 什么时候需要用类变量?

    • 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。比如:定义学生类,统计所有学生共交多少钱。Student(name, static fee)
  • 类变量与实例变量(普通属性)的区别
    • 类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
  • 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  • 类变量可以通过 类名.变量名 或者 对象名.类变量名 来访问,但java设计者推荐我们使用 类名.类变量名方式进行访问。【**前提是 **满足访问修饰符的访问权限和范围】
  • 实例变量不能通过 类名.类变量名 方式访问。
  • 类变量实在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
  • 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

10.1.9 类方法

类方法基本介绍

类方法也叫静态方法

形式如下:
访问修饰符 static 数据返回类型 方法名(){ }

类方法的调用

使用方法:类名.类方法名 或者 对象名.类方法名【前提是 满足访问修饰的访问权限和范围】

public class StaticMethod {public static void main(String[] args) {//创建2个学生对象,叫学费Stu tom = new Stu("tom");//tom.payFee(100);Stu.payFee(100);//对不对?对Stu mary = new Stu("mary");//mary.payFee(200);Stu.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);}
}

10.1.10 类方法经典的使用场景

  • 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发法效率。

    • 比如:工具类中的方法utils Math类、Arrays类、Collections集合类等
  • 小结
    • 在程序员实际开发中,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,直接类名.方法名,比如打印一堆数组,冒泡排序,完成某个计算任务等等。
//如果我们希望不创建实例,也可以调用某个方法(即当做工具来使用)
//这时,把方法做成静态方法时非常合适
System.out.println("9开平方的结果是=" + Math.sqrt(9));System.out.println(MyTools.calSum(10, 30));//开发自己的工具类时,可以将方法做成静态的,方便调用
class MyTools  {//求出两个数的和public static double calSum(double n1, double n2) {return  n1 + n2;}//可以写出很多这样的工具方法...
}

10.1.11 类方法使用注意事项和细节讨论

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this的参数、普通方法中隐含着this的参数。
  2. 类方法可以通过类名调用,也可以通过对象名调用。
  3. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用。
  4. 类方法中不允许使用和对象有关的关键字,比如this和super。但普通方法(成员方法)可以。
  5. 类方法(静态方法)中,只能访问 静态变量 或 静态方法。
  6. 普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
public class StaticMethodDetail {public static void main(String[] args) {D.hi();//ok//非静态方法,不能通过类名调用//D.say();, 错误,需要先创建对象,再调用new D().say();//可以}
}
class D {private int n1 = 100;private static  int n2 = 200;public void say() {//非静态方法,普通方法}public static  void hi() {//静态方法,类方法//类方法中不允许使用和对象有关的关键字,//比如this和super。普通方法(成员方法)可以。//System.out.println(this.n1);}//类方法(静态方法)中 只能访问 静态变量 或静态方法//口诀:静态方法只能访问静态成员.public static void hello() {System.out.println(n2);System.out.println(D.n2);//System.out.println(this.n2);不能使用hi();//OK//say();//错误}//普通成员方法,既可以访问  非静态成员,也可以访问静态成员//小结: 非静态方法可以访问 静态成员和非静态成员public void ok() {//非静态成员System.out.println(n1);say();//静态成员System.out.println(n2);hello();}
}

** 小结:**

静态方法,只能访问静态的成员(属性和方法);非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)

巩固习题

  1. 以下代码输出的结果
public class StaticExercise01 {}class Test {static int count = 9;public void count() {System.out.println("count=" + (count++));}public static void main(String args[]) {new Test().count(); //9new Test().count(); //10System.out.println(Test.count);  //11}
}
  1. 看看下面代码有没有错误,如果有,则修改,看看输出什么结果
class Person {  private int id;private static int total = 0;public static int getTotalPerson() {//id++;//错误, 注销return total;}public Person() {//构造器total++;  //total = 1id = total;//id = 1}
}
class TestPerson {public static void main(String[] args) {System.out.println("Number of total is " +Person.getTotalPerson()); //0Person p1 = new Person();//只有在创建对象时,才调用该类的构造方法System.out.println( "Number of total is "+ Person.getTotalPerson()); //1}
}
  1. 看看下面代码有没有错误,如果有,则修改,看看输出什么结果
class Person { //StaticExercise03.java 2min 看private int id;private static int total = 0;public static void setTotalPerson(int total){// this.total = total;//错误,因为在static方法中,不可以使用this 关键字Person.total = total;}public Person() {//构造器total++;id = total;}//编写一个方法,输出total的值public static void m() {System.out.println("total的值=" + total);}
}
class TestPerson {public static void main(String[] args) {Person.setTotalPerson(3);new Person(); //最后 total的值就是4Person.m();//看看输出的是不是4}
}

小结:
记住三句话
(1) 静态方法,只能访问静态成员
(2) 非静态方法,可以访问所有的成员
(3) 在编写代码时,仍然要遵守访问权限规则

10.2 理解main方法语法

10.2.1 深入理解main方法

解释main方法的形式:public static void main(String[] args){}

  1. main方法是由虚拟机调用
  2. java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
  5. java执行的程序 参数1 参数2 参数3

10.2.2 特别提醒

  1. 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性;
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
public class Main01 {//静态的变量/属性private static  String name = "爱摸鱼的TT~";//非静态的变量/属性private int n1 = 10000;//静态方法public static  void hi() {System.out.println("Main01的 hi方法");}//非静态方法public void cry() {System.out.println("Main01的 cry方法");}public static void main(String[] args) {//可以直接使用 name//1. 静态方法main 可以访问本类的静态成员System.out.println("name=" + name);hi();//2. 静态方法main 不可以访问本类的非静态成员//System.out.println("n1=" + n1);//错误//cry();//3. 静态方法main 要访问本类的非静态成员,需要先创建对象 , 再调用即可Main01 main01 = new Main01();System.out.println(main01.n1);//okmain01.cry();}
}

10.2.3 动态传值

我们看下面这段代码,可以看出其运行结果是无的

public class Main {public static void main(String[] args) {for ( int i = 0; i < args.length; i++ ) {System.out.println("args[" + i + "] = " + args[i]);}}
}

我们可以对其动态传值,也就是以下操作:

以上步骤说白了就是对main方法进行动态传值,运行结果如下:

10.3 代码块

10.3.1 基本介绍

代码化块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来。

但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。

10.3.2 基本语法

[修饰符]{代码
}

说明注意:

  1. 修饰符 可选,要写的话,也只能写static;
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块;
  3. 逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等);
  4. ;号可以写上,也可以省略。

10.3.3 代码块的好处

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  2. 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
public class CodeBlock01 {public static void main(String[] args) {Movie movie = new Movie("你好,李焕英");System.out.println("===============");Movie movie2 = new Movie("唐探3", 100, "陈思诚");}
}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("Movie(String name) 被调用...");this.name = name;}public Movie(String name, double price) {this.name = name;this.price = price;}public Movie(String name, double price, String director) {System.out.println("Movie(String name, double price, String director) 被调用...");this.name = name;this.price = price;this.director = director;}
}

10.3.4 注意事项和细节讨论

  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
  2. 类什么时候被加载【重要背!
    1. 创建对象实例时(new)
    2. 创建子类对象实例,父类也会被加载
    3. 使用类的静态成员时(静态属性,静态方法)
  3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。
public class CodeBlockDetail01 {public static void main(String[] args) {//类被加载的情况举例//1. 创建对象实例时(new)// AA aa = new AA();//2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载// AA aa2 = new AA();//3. 使用类的静态成员时(静态属性,静态方法)// System.out.println(Cat.n1);//static代码块,是在类加载时,执行的,而且只会执行一次.
//        DD dd = new DD();
//        DD dd1 = new DD();// 普通的代码块,在创建对象实例时,会被隐式的调用。// 被创建一次,就会调用一次。// 如果只是使用类的静态成员时,普通代码块并不会执行System.out.println(DD.n1);//8888, 静态模块块一定会执行}
}class DD {public static int n1 = 8888;//静态属性//静态代码块static {System.out.println("DD 的静态代码1被执行...");//}//普通代码块, 在new 对象时,被调用,而且是每创建一个对象,就调用一次//可以这样简单的,理解 普通代码块是构造器的补充{System.out.println("DD 的普通代码块...");}
}class Animal {//静态代码块static {System.out.println("Animal 的静态代码1被执行...");//}
}class Cat extends Animal {public static  int n1 = 999;//静态属性//静态代码块static {System.out.println("Cat 的静态代码1被执行...");//}
}class BB {//静态代码块static {System.out.println("BB 的静态代码1被执行...");//1}
}class AA extends BB {//静态代码块static {System.out.println("AA 的静态代码1被执行...");//2}
}

小结:

  • static代码是类加载时,执行,只会执行一次
  • 普通代码块是在创建对象时调用的,创建一次,调用一次
  • 类加载的3种情况,需要记住。
  1. 创建一个对象时,在一个类调用顺序是:【重点,难点

    1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
public class CodeBlock02 {public static void main(String[] args) {A a = new A();// (1)A 静态代码块01  (2)getN1被调用...}
}
class A{static {//静态代码块System.out.println("A 静态代码块01");}private static int n1 = getN1();public static int getN1(){System.out.println("getN1被调用...");return 100;}
}
  1. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
public class CodeBlock02 {public static void main(String[] args) {A a = new A();// (1)A 静态代码块01  (2)getN1被调用 (3)getN2被调用... (4)A 静态代码块02}
}
class A{private int n2 = getN2();//普通属性的初始化{//普通代码块System.out.println("A 静态代码块02");}static {//静态代码块System.out.println("A 静态代码块01");}private static int n1 = getN1();public static int getN1(){System.out.println("getN1被调用...");return 100;}public int getN2(){System.out.println("getN2被调用...");return 100;}
}
  1. 调用构造方法
public class CodeBlock02 {public static void main(String[] args) {A a = new A();// (1)A 静态代码块01  (2)getN1被调用 (3)getN2被调用... (4)A 静态代码块02 (5)构造器被调用}
}
class A{private int n2 = getN2();//普通属性的初始化{//普通代码块System.out.println("A 静态代码块02");}static {//静态代码块System.out.println("A 静态代码块01");}private static int n1 = getN1();public static int getN1(){System.out.println("getN1被调用...");return 100;}public int getN2(){System.out.println("getN2被调用...");return 100;}public A(){System.out.println("构造器被调用");}
}

【上面所说的执行的顺序一定要记住:理解的记住(静 > 普> 构)】

  1. 构造器 的最前面其实隐含了super()和 调用普通代码块,新写一个类演示【载图+说明】,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于 构造器和普通代码块执行的。
public class CodeBlockDetail03 {public static void main(String[] args) {new BBB();//(1)AAA的普通代码块(2)AAA() 构造器被调用(3)BBB的普通代码块(4)BBB() 构造器被调用}
}class AAA { //父类Object{System.out.println("AAA的普通代码块");}public AAA() {//(1)super()//(2)调用本类的普通代码块System.out.println("AAA() 构造器被调用....");}
}class BBB extends AAA  {{System.out.println("BBB的普通代码块...");}public BBB() {//(1)super()//(2)调用本类的普通代码块System.out.println("BBB() 构造器被调用....");}
}

用子类构造器 —> 调用super —> 执行父类静块 —> 执行父类普块 —> 父类输出语句 —> 子类静块 —> 子类普块 —> 子类输出语句

  1. 我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:

    1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    4. 父类的构造方法
    5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    6. 子类的构造方法 //面试
public class CodeBlockDetail04 {public static void main(String[] args) {//老师说明//(1) 进行类的加载//1.1 先加载 父类 A02 1.2 再加载 B02//(2) 创建对象//2.1 从子类的构造器开始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() {//构造器//隐藏//super()//普通代码和普通属性的初始化......System.out.println("A02的构造器");//(7)}}class B02 extends A02 { //private static int n3 = getVal03();static {System.out.println("B02的一个静态代码块..");//(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() {//构造器//隐藏了//super()//普通代码块和普通属性的初始化...System.out.println("B02的构造器");//(10)// TODO Auto-generated constructor stub}
}
  1. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
public class CodeBlockDetail04 {public static void main(String[] args) {new C02();// 200 100 200}
}
class C02 {private int n1 = 100;private static  int n2 = 200;private void m1() {}private static void m2() {}static {//静态代码块,只能调用静态成员//System.out.println(n1);错误System.out.println(n2);//ok//m1();//错误m2();}{//普通代码块,可以使用任意成员System.out.println(n1);System.out.println(n2);//okm1();m2();}
}

巩固习题

下面代码输出什么?

public class CodeBlockExercise02 {}class Sample
{Sample(String s){System.out.println(s);}Sample(){System.out.println("Sample默认构造函数被调用");}
}
class Test{Sample sam1=new Sample("sam1成员初始化");//static Sample sam=new Sample("静态成员sam初始化 ");//static{System.out.println("static块执行");//if(sam==null)System.out.println("sam is null");}Test()//构造器{System.out.println("Test默认构造函数被调用");//}//主方法public static void  main(String  str[]){Test a=new Test();//无参构造器}}

运行结果:

1. 静态成员 sam 初始化
2. static 块执行
3. sam1 成员初始化
4. Test 默认构造函数被调用

10.4 单例模式

10.4.1 什么是设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己在思考和摸索

10.4.2 什么是单例模式

单例二字简单来说就是单个的实例

  1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法;
  2. 单例模式有两种方式
    1. 饿汉式
    2. 懒汉式

10.4.3 单例模式应用实例

步骤:

  1. 构造器私有化 => 防止直接new
  2. 类的内部创建对象(私有静态对象)
  3. 向外暴露一个静态的公共方法。getInstance => 为了获取对象
  4. 代码实现
  • 饿汉式:(饥渴,不管是否需要用到对象 都会创建对象)在类加载已经创建实例该对象,但有可能没有被使用到。
public class SingleTo01 {public static void main(String[] args) {//通过方法可以获取对象GirlFriend gf = GirlFriend.getInstance();System.out.println(gf);}}
//有一个类,GirlFriend
//只能有一个女朋友
class GirlFriend{private String name;//为了能够在静态方法中,返回 girlFriend对象,需要将其修饰为static//对象,通常是重量級的對象, 饿汉式可能造成创建了对象,但是有可能沒有被使用。//主要是用static修饰的,不管是否用到此对象,都会随着类加载而加载。private static GirlFriend girlFriend = new GirlFriend("小涛涛");//如何保障我们只能创建一个 GirlFriend 对象//步骤[单例模式-饿汉式]//1. 将构造器私有化//2. 在类的内部直接创建对象(该对象是static)//3. 提供一个公共的static方法,返回 girlFriend对象private GirlFriend(String name){this.name = name;}public static GirlFriend getInstance(){return girlFriend;}@Overridepublic String toString() {return "GirlFriend{" +"name='" + name + '\'' +'}';}
}
  • 懒汉式:(懒惰,需要就创建)需要使用该对象时,就创建实例。
public class SingleTo02 {public static void main(String[] args) {Cat ct = Cat.getInstance();System.out.println(ct);}
}//希望在程序运行过程中,只能创建一个cat对象
//使用單例模式
class Cat{private String name;private static Cat cat ;//默认是null//步驟//1.仍然构造器私有化//2.定义一个static靜态屬性對象//3.提供一個public的static方法,可以返回一個Cat對象//4.懶漢式,只有当用户使用getInstance時,才返回cat對象, 后面再次调用时,会返回上次創建的cat對象//  从而保证了单例private Cat(String name){this.name = name;}public static Cat getInstance(){if(cat == null){//如果還沒有創建cat對象cat = new Cat("小可爱");}return cat;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\'' +'}';}
}

10.4.4 饿汉式 VS 懒汉式

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式在类加载就创建了对象实例,而懒汉式是在使用时才创建。
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程时,会完善)
  3. 饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
  4. 在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式。


10.5 final关键字

10.5.1 基本介绍

final中文意思:最后的,最终的
在某些情况下,程序员可能有以下需求,就会使用final:

  1. 当不希望类被继承时,可以用final修饰
  2. 但不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰
  3. 但不希望类的某个属性的值被修改,可以用final修饰。(public final double TAX_RATE = 0.08)
  4. 但不希望某个局部变量被修改,可以使用final修饰,(final double TAX_RATE = 0.08)

10.5.2 final使用注意事项和细节讨论

  1. final修饰的属性又叫常量,一般用 XX_XX_XX_来命名
  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一(选择一个位置赋值即可)
    1. 定义时:如public final double TAX_RATE = 0.08;
    2. 在构造器中
    3. 在代码块中
  3. 如果final修饰的属性是静态的,则初始化的位置只能是:
    1. 定义时
    2. 在静态代码块,不能在构造器中赋值
  4. final类不能继承,但可以实例化对象
  5. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
  6. 一般来说,如果一个类已经是final类,就没有必要再将方法修饰成final方法
  7. final不能修饰构造方法(即构造器)
  8. final和static往往搭配使用,效率更高,不会导致类加载(因为底层编译器做了优化处理)
class Demo{public static final int i = 16;static{System.out.println("涛涛");}
}
  1. 包装类(Integer,Double,Float,Boolean等都是final),String也是final类

巩固练习

请编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写以下。

public class FinalExercise01 {public static void main(String[] args) {Circle circle = new Circle(5.0);System.out.println("面积=" + circle.calArea());}
}class Circle {private double radius;private final double PI;// = 3.14;//构造器public Circle(double radius) {this.radius = radius;//PI = 3.14;}{PI = 3.14;}public double calArea() {return PI * radius * radius;}
}

10.6 抽象类

10.6.1 问题引出

public class Abstract01 {public static void main(String[] args) {}
}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()  ;
}

10.6.2 解决之道

当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。

我们看看如何将Animal做成抽象类

abstract class Animal{String name;int age;abstract public void cry();
}

10.6.3 抽象类的介绍

  1. 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{}
  1. 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
  1. 抽象类的价值更多作用于设计,是设计者设计好后,让子类继承并实现抽象类()
  2. 抽象类,是面试比较爱问的知识点,在框架和设计模式使用较多

10.6.4 抽象类使用的注意事项和细节讨论

  1. 抽象类不能被实例化

  2. 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法

  3. 一旦类包含了abstract方法,则这个类必须声明为abstract

  4. abstract只能修饰类和方法,不能修饰属性和其他的

  5. 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等

  6. 抽象方法不能有主体,即不能实现。

  7. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类

  8. 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。

public class AbstractDetail {public static void main(String[] args) {System.out.println("hello");}
}
//抽象方法不能使用private、final 和 static来修饰,因为这些关键字都是和重写相违背的
abstract class H {public   abstract void hi();//抽象方法
}//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class E {public abstract void hi();
}
abstract class F extends E {}
class G extends E {@Overridepublic void hi() { //这里相等于G子类实现了父类E的抽象方法,所谓实现方法,就是有方法体}
}//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {public int n1 = 10;public static  String name = "爱摸鱼的TT~";public void hi() {System.out.println("hi");}public abstract void hello();public static void ok() {System.out.println("ok");}
}

巩固练习

编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于Manager类类来说,他既是员工,还是有奖金(bonus)的属性。使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性方法吗,实现work(),要求类中 必要的方法进行属性访问,提示经理/普通员工名字 工作中…”OOP的继承 和 抽象类。

10.7 抽象类最佳实践 - 模板设计模式

10.7.1 基本介绍

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

10.7.2 模板设计模式能解决的问题

  1. 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去(也就是说使用抽象定义),让子类去实现。
  2. 编写一个抽象父类,父类提供多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。

10.7.3 最佳实践

需求:

  1. 有多个类,完成不同的任务job
  2. 要求统计得到各自完成任务的时间
  3. 请编程实现

Template类:abstract public class Template { //抽象类-模板设计模式public abstract void job();//抽象方法public void calculateTime() {//实现方法,调用job方法//得到开始的时间(毫秒)long start = System.currentTimeMillis();job(); //动态绑定机制//得的结束的时间long end = System.currentTimeMillis();System.out.println("任务执行时间 " + (end - start));}
}AA类:public class AA extends Template {//计算任务//1+....+ 800000@Overridepublic void job() { //实现Template的抽象方法joblong num = 0;for (long i = 1; i <= 800000; i++) {num += i;}}
}BB类:public class BB extends Template{public void job() {//这里也去,重写了Template的job方法long num = 0;for (long i = 1; i <= 80000; i++) {num *= i;}}
}TestTemplate类:public class TestTemplate {public static void main(String[] args) {AA aa = new AA();aa.calculateTime(); //这里还是需要有良好的OOP基础,对多态BB bb = new BB();bb.calculateTime();}
}

10.8 接口

10.8.1 为什么有接口

10.8.2 接口的快速入门

使用接口的设计需求在java编程/php/.net/go中也是会大量存在的,我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序模拟一下上面usb插槽的接口实现:

public interface UsbInterface { // 接口//规定接口的相关方法public void start();public void stop();
}public class Phone implements UsbInterface{@Overridepublic void start() {System.out.println("手机开始工作...");}@Overridepublic void stop() {System.out.println("手机停止工作...");}
}public class Camera implements UsbInterface{//实现接口,就是把接口方法实现@Overridepublic void start() {System.out.println("相机开始工作...");}@Overridepublic void stop() {System.out.println("相机停止工作...");}
}public class Computer {//编写一个方法, 计算机工作//解读://1. UsbInterface usbInterface 形参是接口类型 UsbInterface//2. 看到 接收 实现了 UsbInterface接口的类的对象实例public void work(UsbInterface usbInterface){//通过接口,来调用方法usbInterface.start();usbInterface.stop();}
}public class Interface01 {public static void main(String[] args) {//创建手机,相机对象//Phone 实现了 UsbInterfacePhone phone = new Phone();//Camera 实现了 UsbInterfaceCamera camera = new Camera();//创建计算机Computer computer = new Computer();computer.work(phone);//把手机接入到计算机System.out.println("===============");computer.work(camera);//把相机接入到计算机}
}

10.8.3 基本介绍

接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。语法:

interface 接口名{//属性//方法(1.抽象方法 2.默认实现方法(必须要用default修饰) 3.静态方法)
}class 类名 implements 接口{自己属性;自己方法;必须实现的接口的抽象方法;
}

小结:
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体(jdk7.0)。接口体现了程序设计的多态和高内聚低耦合的设计思想。

特殊情况:jdk8.0后接口类可以又静态方法,默认方法,也就是说接口中可以有方法的具体实现。

public interface AInterface {//写属性public int n1 = 10;//写方法//在接口中,抽象方法,可以省略abstract关键字public void hi();//在jdk8后,可以有默认实现方法,需要使用default关键字修饰default public void ok() {System.out.println("ok ...");}//在jdk8后, 可以有静态方法public static void cry() {System.out.println("cry ....");}
}

10.8.4 深入讨论

对于初学者,理解借口的概念不算太难,难的是不知道什么时候使用接口,下面我就举例一个应用场景:

说现在又一个项目经理,管理三个程序员。功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。
实际需求:3个程序员,编写三个类,分贝王城对Mysql、Oracle、DB2数据库的连接connect、close方法。

public interface DBInterface {//项目经理public void connect();//连接方法public void close();//关闭连接
}//A程序员连接Mysql
public class MysqlDB implements DBInterface{@Overridepublic void connect() {System.out.println("连接mysql");}@Overridepublic void close() {System.out.println("关闭mysql");}
}//B程序员连接Oracle
public class OracleDB implements DBInterface{@Overridepublic void connect() {System.out.println("连接oracle");}@Overridepublic void close() {System.out.println("关闭oracle");}
}public class Interface03 {public static void main(String[] args) {MysqlDB mysqlDB = new MysqlDB();t(mysqlDB);OracleDB oracleDB = new OracleDB();t(oracleDB);}//用static修饰就不用new对象了public static void t(DBInterface db) {db.connect();db.close();}
}

10.8.5 注意事项和细节

  1. 接口不能被实例化
  2. 接口中所有方法是public方法,这个public可以省略,接口中抽象方法,也是可以不用写abstract修饰
  3. 一个普通类实现接口,就必须将该接口的所有方法都实现
  4. 抽象类实现接口,可以不用实现接口的方法
  5. 一个类同时可以实现多个接口
  6. 接口中的属性,只能是final的,而且是public static final 修饰符。比如 int a = 1;实际上是 public static final int a = 1;(必须初始化)
  7. 接口中属性的访问形式:接口名.属性名
  8. 接口不能继承其他的类,但是可以继承多个别的接口。(简单说就是 接口和接口之间谈继承,而接口和类谈实现)例如:interface A extends B,C{ }
  9. 接口的修饰符 只能是public 和 默认,这点和类的修饰符是一样的。

10.8.6 实现接口 vs 继承类

首先,我们举个例子:现在有一只猴子,猴子本身就是会爬树,其固有属性,那就可以直接继承它,但猴子不会飞也不会游泳,但是想获得这些方法,那就要学习这些方法,即实现飞行和游泳的接口。

public class ExtendsVsInterface {public static void main(String[] args) {LittleMonkey wuKong = new LittleMonkey("悟空");wuKong.climbing();wuKong.swimming();wuKong.flying();}
}//猴子
class Monkey {private String name;public Monkey(String name) {this.name = name;}public void climbing() {System.out.println(name + " 会爬树...");}public String getName() {return name;}
}//接口
interface Fishable {void swimming();
}
interface Birdable {void flying();
}//继承
//小结:  当子类继承了父类,就自动的拥有父类的功能
//      如果子类需要扩展功能,可以通过实现接口的方式扩展.
//      可以理解 实现接口 是 对java 单继承机制的一种补充.
class LittleMonkey extends Monkey implements Fishable,Birdable {public LittleMonkey(String name) {super(name);}@Overridepublic void swimming() {System.out.println(getName() + " 通过学习,可以像鱼儿一样游泳...");}@Overridepublic void flying() {System.out.println(getName() + " 通过学习,可以像鸟儿一样飞翔...");}
}

小结:

  1. 接口和继承解决的问题不同

    1. 继承的价值主要在于:解决代码的复用性和可维护性
    2. 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活
  2. 接口比继承更加灵活

    1. 接口比继承更加灵活,继承是满足 **is-a **的关系,而接口只需要满足 **like-a **的关系
  3. 接口在一定程度上实现代码解耦【即:接口规范性+动态绑定机制】

10.8.7 接口的多态特性

  1. 多态参数

    1. 在前面的Usb接口案例,UsbInterface usb,既可以接收手机对象,又可以接收相机对象,就体现了 接口 多态(接口引用可以指向实现了接口的类的对象
public class InterfacePolyParameter {public static void main(String[] args) {//接口的多态体现//接口类型的变量 if01 可以指向 实现了IF接口类的对象实例IF if01 = new Monster();if01 = new Car();//对比下继承中的多态//继承体现的多态//父类类型的变量 a 可以指向 继承AAA的子类的对象实例AAA a = new BBB();a = new CCC();}
}interface IF {}
class Monster implements IF{}
class Car implements  IF{}class AAA {}
class BBB extends AAA {}
class CCC extends AAA {}
  1. 多态数组

    1. 举例:给Usb数组中,存放Phone 和 相机对象,Phone类还有一个特有的方法call( ),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call
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("手机可以打电话...");}@Overridepublic void work() {System.out.println("手机工作中...");}
}
class Camera_ implements Usb {@Overridepublic void work() {System.out.println("相机工作中...");}
}

  1. 接口存在多态传递现象
/*** 演示多态传递现象*/
public class InterfacePolyPass {public static void main(String[] args) {//接口类型的变量可以指向,实现了该接口的类的对象实例IG ig = new Teacher();//如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口//那么,实际上就相当于 Teacher 类也实现了 IH接口.//这就是所谓的 接口多态传递现象.IH ih = new Teacher();}
}interface IH {void hi();
}
interface IG extends IH{ }//这部分显示 接口和接口的继承
class Teacher implements IG {@Overridepublic void hi() {}
}

巩固练习

public class InterfaceExercise01 {public static void main(String[] args) {}
}interface A {  // 1min 看看int x = 0;
}  //想到 等价 public static final int x = 0;class B {int x = 1;
} //普通属性class C extends B implements A {public void pX() {//System.out.println(x); //错误,原因不明确x//可以明确的指定x//访问接口的 x 就使用 A.x//访问父类的 x 就使用 super.xSystem.out.println(A.x + " " + super.x);}public static void main(String[] args) {new C().pX();}
}

接口小结

类的五大成员

  • 属性
  • 方法
  • 构造器
  • 代码块

最后一个类的成员就是接下来所讲解重点难点—内部类

10.9 内部类

  • 如果定义类的局部位置(方法中/代码块)(1)局部内部类(2)匿名内部类
  • 定义在成员位置(1)成员内部类(2)静态内部类

10.9.1 基本介绍

一个类的内部有完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的五大成员【思考:类的五大成员是哪些?[ 属性、方法、构造器、代码块、内部类 ]】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系,注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。

10.9.2 基本语法

class Outer{//外部类class Inner{//内部类}
}class Other{//外部其他类
}
public class InnerClass01 { //外部其他类public static void main(String[] args) {}
}
class Outer { //外部类private int n1 = 100;//属性public Outer(int n1) {//构造器this.n1 = n1;}public void m1() {//方法System.out.println("m1()");}{//代码块System.out.println("代码块...");}class Inner { //内部类, 在Outer类的内部}
}

10.9.3 内部类的分类

  1. 定义在外部类的局部位置上(比如方法内)

    1. 局部内部类(有类名)
    2. 匿名内部类(没有类名,重点!!!!!)
  2. 定义在外部类的成员位置上
    1. 成员内部类(没有static修饰)
    2. 静态内部类(使用static修饰)

10.9.4 局部内部类的使用

说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

  1. 可以直接访问外部类的所有成员,包含私有的
  2. 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量也可以使用final,不被继承
  3. 作用域:仅仅在定义它的方法或代码块中
  4. 局部内部类—访问—>外部类的成员【访问方式:直接访问】
  5. 外部类—访问—>局部内部类的成员【访问方式:创建内部类的对象,再访问(注意:必须在作用域内)】

记住:(1)局部内部类定义在方法中/代码块
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类

public class LocalInnerClass {public static void main(String[] args) {//演示一遍Outer01 outer01 = new Outer01();outer01.m1();}
}
class Outer01{//外部类private int n1 = 10;private void m2(){//私有方法System.out.println("Outer01");}public void m1(){//方法//1.局部内部类是定义在外部类的局部位置,通常在方法//3.不能添加访问修饰符,但是可以使用final 修饰//4.作用域 : 仅仅在定义它的方法或代码块中final class Inner01{//局部内部类(本质仍然是一个类)//2.可以直接访问外部类的所有成员,包含私有的public void f1(){//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()System.out.println("n1=" + n1);m2();}}//6. 外部类在方法中,可以创建Inner02对象,然后调用方法即可Inner01 inner01 = new Inner01();inner01.f1();}{//代码块class Inner02{}}
}

  1. 外部其他类—不能访问—>局部内部类(因为局部内部类地位是一个局部变量)
  2. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用【外部类名.this.成员】去访问
System.out.println("外部类的n2=" + 外部类名.this.n2);
public class LocalInnerClass {public static void main(String[] args) {//演示一遍Outer01 outer01 = new Outer01();outer01.m1();System.out.println("outer02的hashcode=" + outer01);}
}
class Outer01{//外部类private int n1 = 10;private void m2(){//私有方法System.out.println("Outer01");}public void m1(){//方法//1.局部内部类是定义在外部类的局部位置,通常在方法//3.不能添加访问修饰符,但是可以使用final 修饰//4.作用域 : 仅仅在定义它的方法或代码块中final class Inner01{//局部内部类(本质仍然是一个类)private int n1 = 80;//2.可以直接访问外部类的所有成员,包含私有的public void f1(){//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类n1 和 m2()System.out.println("n1=" + n1 + " 外部类的n1=" + Outer01.this.n1);//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,//   使用 (外部类名.this.成员)去访问//   解读 Outer01.this 本质就是外部类的对象, 即哪个对象调用了m1, Outer01.this就是哪个对象System.out.println("n1=" + n1 + " 外部类的n1=" + Outer01.this.n1);System.out.println("Outer02.this hashcode=" + Outer01.this);m2();}}Inner01 inner01 = new Inner01();inner01.f1();}{//代码块class Inner02{}}
}

10.9.5 匿名内部类的使用(重要!!!)

(1)本质是类 (2)内部类 (3)该类没有名字(其实吧,是有名字的,系统分配的,jdk底层可以显示出来) (4)同时还是一个对象

说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名

  1. 匿名内部类的基本语法
new 类或接口(类的参数列表){类体/接口 重写
};

基于接口的匿名内部类

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. 传统方式,是写一个类,实现该接口,并创建对象
//        IA tiger = new Tiger();
//        tiger.cry();//3. 目的 需求是 Tiger/Dog 类只是使用一次,后面再不使用,那采取匿名内部类//4. 可以使用匿名内部类来简化开发//问题:(一定要理解掌握)//5. tiger的编译类型(等号的左边) ? IA//6. tiger的运行类型(实例对象) ? 就是匿名内部类  Outer04$1【看底层原理】/*我们看底层 系统会分配 类名 Outer04$1class Outer04$1 implements IA {@Overridepublic void cry() {System.out.println("老虎叫唤...");}}*///7. jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1实例,并且把地址//   返回给 tiger//8. 匿名内部类使用一次,就不能再使用IA tiger = new IA() {// {}里面的相当于Tiger类中重写的方法@Overridepublic void cry() {System.out.println("老虎叫唤...");}};System.out.println("tiger的运行类型=" + tiger.getClass());//getClass()是用于获取运行类型地址tiger.cry();tiger.cry();tiger.cry();}
}interface IA {//接口public void cry();
}
//class Tiger implements IA {//
//    @Override
//    public void cry() {//        System.out.println("老虎叫唤...");
//    }
//}

基于类的匿名内部类

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. father编译类型 Father//2. father运行类型 Outer04$2//3. 底层会创建匿名内部类/*class Outer04$2 extends Father{@Overridepublic void test() {System.out.println("匿名内部类重写了test方法");}}*///4. 同时也直接返回了 匿名内部类 Outer04$2的对象//5. 注意("jack") 参数列表会传递给 构造器Father father = new Father("jack"){@Overridepublic void test() {System.out.println("匿名内部类重写了test方法");}};System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2father.test();}
}class Father {//类public Father(String name) {//构造器System.out.println("接收到name=" + name);}public void test() {//方法}
}

基于抽象类的匿名内部类

public class AnonymousInnerClass {public static void main(String[] args) {Outer04 outer04 = new Outer04();outer04.method();}
}class Outer04 { //外部类private int n1 = 10;//属性public void method() {//方法//基于抽象类的匿名内部类Animal animal = new Animal() {@Overridevoid eat() {System.out.println("小狗吃骨头...");}};animal.eat();}
}abstract class Animal { //抽象类abstract void eat();
}

  1. 匿名内部类的语法比较奇怪,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类的方法。
public class AnonymousInnerClassDetail {public static void main(String[] args) {Outer05 outer05 = new Outer05();outer05.f1();}
}class Outer05 {private int n1 = 99;public void f1() {//第一种:// 创建一个基于类的匿名内部类//不能添加访问修饰符,因为它的地位就是一个局部变量//作用域 : 仅仅在定义它的方法或代码块中Person p = new Person(){private int n1 = 88;@Overridepublic void hi() {System.out.println("匿名内部类重写了 hi方法");}};p.hi();//动态绑定, 运行类型是 Outer05$1//第二种:// 也可以直接调用, 匿名内部类本身也是返回对象// class 匿名内部类 extends Person {}new Person(){@Overridepublic void hi() {System.out.println("匿名内部类重写了 hi方法,哈哈...");}@Overridepublic 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);}
}
//抽象类/接口...

  1. 可以直接访问外部类的所有成员,包含私有的

  2. 不能添加访问修饰符,因为它的地位就是一个局部变量

  3. 作用域:仅仅在定义它的方法或代码块中,因为匿名内部类只能使用一次,使用完就没了

  4. 匿名内部类—>访问—>外部类成员【访问方式:直接访问】

  5. 外部其他类—>不能访问—>匿名内部类(因为 匿名内部类地位是一个局部变量)

  6. 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用【外部类名.this.成员】去访问

【以上这几点在10.9.4中有讲解】

匿名内部类的最佳实战

  1. 当做实参直接传递,简洁高效。
public class InnerClassExercise01 {public static void main(String[] args) {//当做实参直接传递,简洁高效(仅一次使用)f1(new IL() {@Overridepublic void show() {System.out.println("这是一副名画~~...");}});//传统方法f1(new Picture());}//静态方法,形参是接口类型public static void f1(IL il) {il.show();}
}
//接口
interface IL {void show();
}//传统方式
//类->实现IL => 编程领域 (硬编码)(方便多次使用)
class Picture implements IL {@Overridepublic void show() {System.out.println("这是一副名画XX...");}
}
  1. 实现下面要求:

    1. 有一个铃声接口Bell,里面有个ring方法。
    2. 有一个手机类Cellphone,具有闹钟功能alarmClock,参数是Bell类型
    3. 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
    4. 再传入另一个匿名内部类(对象),打印:小伙伴上课了
public class InnerClassExercise02 {public static void main(String[] args) {/*1.有一个铃声接口Bell,里面有个ring方法。2.有一个手机类Cellphone,具有闹钟功能alarmClock,参数是Bell类型3.测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了4.再传入另一个匿名内部类(对象),打印:小伙伴上课了*/CellPhone cellPhone = new CellPhone();//解读//1. 传递的是实现了 Bell接口的匿名内部类 InnerClassExercise02$1//2. 重写了 ring//3. Bell bell = new Bell() {//            @Override//            public void ring() {//                System.out.println("懒猪起床了");//            }//        }cellPhone.alarmClock(new Bell() {@Overridepublic void ring() {System.out.println("懒猪起床了");}});cellPhone.alarmClock(new Bell() {@Overridepublic void ring() {System.out.println("小伙伴上课了");}});}
}
interface Bell{ //接口void ring();//方法
}
class CellPhone{//类public void alarmClock(Bell bell){//形参是Bell接口类型System.out.println(bell.getClass());bell.ring();//动态绑定}
}

以上就是匿名内部类的相关知识点和习题,难度较大,需要大家多花时间去掌握,所用到的知识点有:

  • 继承
  • 多态
  • 动态绑定
  • 内部类

10.9.6 成员内部类的使用

说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。

  1. 可以直接访问外部类的所有成员,包含私有的。
public class MemberInnerClass01 {public static void main(String[] args) {Outer08 outer08 = new Outer08();outer08.t1();}
}
class Outer08{//外部类private int n1 = 10;public String name = "张三";//注意: 成员内部类,是定义在外部内的成员位置上class Inner08{//成员内部类public void say(){//可以直接访问外部类的所有成员,包含私有的System.out.println("n1 = " + n1 + " name =" + name);}}//写方法public void t1(){//使用成员内部类Inner08 inner08 = new Inner08();inner08.say();}
}

  1. 成员内部类可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

  2. 作用域,和外部类的其他成员一样,为整个类体比如前面代码(外部类)所说,在外部类的成员方法中创建成员内部类对象,再调用方法。

  3. 成员内部类 —> 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】(说明)

  4. 外部类 —> 访问 —> 成员内部类(说明) 【访问方式:创建对象,再访问】

public class MemberInnerClass01 {public static void main(String[] args) {Outer08 outer08 = new Outer08();outer08.t1();}
}
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...public class Inner08{//成员内部类private double sal = 99.8;public void say(){//可以直接访问外部类的所有成员,包含私有的System.out.println("n1 = " + n1 + " name = " + name);hi();}}//写方法public void t1(){//外部类 使用成员内部类//创建成员内部类的对象,然后使用相关的方法Inner08 inner08 = new Inner08();inner08.say();System.out.println(inner08.sal);}
}

  1. 外部其他类 —> 访问 —> 成员内部类【共3种方式,其实就2种】
public class MemberInnerClass01 {public static void main(String[] args) {Outer08 outer08 = new Outer08();outer08.t1();//外部其它类,使用成员内部类的三种方式//解读//第一种方式:// 【new Inner08()】成员  outer08.new Inner08();相当于把new Inner08()当做是outer08成员//这就是一个语法,不要特别纠结。Outer08.Inner08 inner08 = outer08.new Inner08();inner08.say();//第二种方式:在外部类中,编写一个方法,可以返回Inner08对象Outer08.Inner08 inner08Instance = outer08.getInner08Instance();inner08Instance.say();//第三种方式:本质就是第一种,就是匿名new个对象Outer08.Inner08 inner081 = new Outer08().new Inner08();inner081.say();}
}
...
  1. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。

10.9.7 静态内部类的使用

说明:静态内部类是定义在外部类的成员位置,并且有static修饰

  1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态的成员
  2. 可以添加任意访问修饰符(public,protected,默认,private),因为它的地位就是一个成员
  3. 作用域:同其他的成员,为整个类体
  4. 静态内部类 —> 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
  5. 外部类 —> 访问 —> 静态内部类【访问方式:创建对象,再访问】

  1. 外部其它类 —> 访问 —> 静态内部类(共2种方式)

  1. 如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。

内部类小结

(1)内部类有四种:局部内部类,匿名内部类,成员内部类,静态内部类
(2)重点还是掌握 匿名内部类使用
new 类/接口(参数列表){ //…}
(3)成员内部类,静态内部类,是放在外部类的成员位置上,本质就是一个成员。
(4)其他细节看以上笔记…

巩固练习

  1. 按要求编写程序(静态成员的使用)【易】

    1. 在Frock类中声明私有的静态属性currentNum[int类型],初始值为100000,作为衣服出厂的序列号起始值。
    2. 声明公有的静态方法getNextNum,作为生成上衣唯一序列号的方法。每调用一次,将currentNum增加100,并作为返回值。
    3. 在TestFrock类的main方法中,分两次调用getNextNum方法,获取序列号并打印输出。
    4. 在Frock类中声明serialNumber(序列号)属性,并提供对应的get方法;
    5. 在Frock类的构造器中,通过调用getNextNum方法为Frock对象获取唯一序列号,赋给serialNumber属性。
    6. 在TestFrock类的main方法中,分别创建三个Frock 对象,并打印三个对象的序列号,验证是否为按100递增
public class TestFrock {public static void main(String[] args) {System.out.println(Frock.getNextNum());//100100System.out.println(Frock.getNextNum());//100200Frock frock = new Frock();//序列号就是 100300Frock frock1 = new Frock();//序列号就是 100400Frock frock2 = new Frock();//序列号就是 100500System.out.println(frock.getSerialNumber());//100300System.out.println(frock1.getSerialNumber());//100400System.out.println(frock2.getSerialNumber());//100500}
}
/*
1.在Frock类中声明私有的静态属性currentNum[int类型],初始值为100000,作为衣服出厂的序列号起始值。
2.声明公有的静态方法getNextNum,作为生成上衣唯一序列号的方法。每调用一次,将currentNum增加100,并作为返回值。
3.在TestFrock类的main方法中,分两次调用getNextNum方法,获取序列号并打印输出。
4.在Frock类中声明serialNumber(序列号)属性,并提供对应的get方法;
5.在Frock类的构造器中,通过调用getNextNum方法为Frock对象获取唯一序列号,赋给serialNumber属性。
6.在TestFrock类的main方法中,分别创建三个Frock 对象,并打印三个对象的序列号,验证是否为按100递增*/
class Frock{private static int currentNum = 100000;private int serialNumber;public Frock() {serialNumber = getNextNum();}public static int getNextNum(){currentNum += 100;//将currentNum增加100return currentNum;}public int getSerialNumber() {return serialNumber;}
}
  1. 按要求完成(抽象类的使用)【易】

    1. 动物类Animal包含了抽象方法 shout();
    2. Cat类继承了Animal,并实现方法shout,打印“猫会喵喵叫”
    3. Dog类继承了Animal,并实现方法shout,打印“狗会汪汪叫”
    4. 在测试类中实例化对象Animal cat =new Cat(),并调用cat的shout方法
    5. 在测试类中实例化对象Animal dog=new Dog(),并调用dog的shout方法
public class HomeWork02 {public static void main(String[] args) {Animal cat = new Cat();cat.shout();Animal dog = new Dog();dog.shout();}
}
/*
按要求实现下列问题: 1min完成 -> 抽象类的使用
动物类Animal包含了抽象方法  shout();
Cat类继承了Animal,并实现方法shout,打印“猫会喵喵叫”
Dog类继承了Animal,并实现方法shout,打印“狗会汪汪叫”
在测试类中实例化对象Animal cat =new  Cat(),并调用cat的shout方法
在测试类中实例化对象Animal dog=new  Dog(),并调用dog的shout方法*/
abstract class Animal{//抽象类public abstract void shout();
}
class Cat extends Animal{@Overridepublic void shout() {System.out.println("猫会喵喵叫");}
}
class Dog extends Animal{@Overridepublic void shout() {System.out.println("狗会汪汪叫");}
}
  1. 按要求完成(匿名内部类的使用)

    1. 计算器接口具有work方法,功能是运算,有一个手机类CellPhone,定义方法testWork测试计算功能,调用计算接口的work方法
    2. 要求调用CellPhone对象的testWork方法,使用上匿名内部类
public class HomeWork03 {public static void main(String[] args) {CellPhone cellPhone = new CellPhone();//解读//1. 匿名内部类是指哪个/*new ICalculate() {@Overridepublic double work(double n1, double n2) {return n1 + n2;}}, 同时也是一个对象他的编译类型 ICalculate, 他的运行类型就是 匿名内部类*/cellPhone.testWork(new ICalculate() {@Overridepublic double work(double n1, double n2) {return n1 + n2;}},20,80);// 100.0cellPhone.testWork(new ICalculate() {@Overridepublic double work(double n1, double n2) {return n1 * n2;}},20,80);// 1600.0}
}
/*
1.计算器接口具有work方法,功能是运算,有一个手机类Cellphone,定义方法testWork测试计算功能,调用计算接口的work方法,
2.要求调用CellPhone对象 的testWork方法,使用上 匿名内部类*/
//编写接口
interface ICalculate{//work方法 是完成计算,但是题没有具体要求,所以自己设计。就不具体定义做加减乘除哪个计算了//至于该方法完成怎样的计算,我们交给匿名内部类完成public double work(double n1, double n2);
}
class CellPhone{//解读,当我们调用testWork方法时,直接传入一个实现了ICalculate接口的匿名内部类即可//该匿名内部类,可以灵活的实现work,完成不同的计算任务//为什么要传入形参n1 n2呢?因为要调用接口的work方法,sopublic void testWork(ICalculate iCalculate, double n1, double n2){double result = iCalculate.work(n1, n2);//动态绑定System.out.println("计算的结果是: " + result);}
}
  1. 按要求完成(综合性)【 需求---->理解---->代码–>优化】【难】

    1. 有一个交通工具接口类Vehicles,有work接口
    2. 有Horse类和Boat类分别实现Vehicles接口类
    3. 创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat
    4. 有Person类,有name和Vehicles属性,在构造器中为两个属性赋值
    5. 实例化Person
    6. 对象“唐僧”,要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具
    7. 增加一个情况,如果唐僧过火焰山, 使用 飞机 ==> 程序扩展性, 我们前面的程序结构就非常好扩展

Vehicles接口类:

public interface Vehicles{//有一个交通工具接口类Vehicles,有work接口public void work();
}

Horse类:

public class Horse implements Vehicles{@Overridepublic void work() {System.out.println(" 一般情况下,使用马儿前进...");}
}

Boat类:

public class Boat implements Vehicles{@Overridepublic void work() {System.out.println(" 过河的时候,使用小船.. ");}
}

Plane类:

public class Plane implements Vehicles {@Overridepublic void work() {System.out.println("过火焰山,使用飞机...");}
}

VehiclesFactory工厂类:

public class VehiclesFactory {//马儿始终是同一匹private static Horse horse = new Horse(); //饿汉式//为了防止创建该对象,就需进行私有化private VehiclesFactory() {}//创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat//这里,我们将方法做成staticpublic static Horse getHorse(){//return new horse();return horse;}public static Boat getBoat() {return new Boat();}public static Plane getPlane() {return new Plane();}
}

Person类:

public class Person {private String name;private Vehicles vehicles;//在创建人对象时,事先给他分配一个交通工具public Person(String name, Vehicles vehicles) {this.name = name;this.vehicles = vehicles;}//实例化Person对象“唐僧”,要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具//这里涉及到一个编程思路,就是可以把具体的要求,封装成方法-> 这里就是编程思想//思考一个问题,如何不浪费,在构建对象时,传入的交通工具对象->动脑筋public void passRiver() {//先得到船/*没有进行vehicles判定,属实浪费了Boat boat = VehiclesFactory.getBoat();boat.work();*///判断一下,当前的 vehicles 属性是null, 就获取一艘船//如何防止始终使用的是传入的马 解决方法:instanceOf//if(vehicles == null){//解说://vehicles instanceof Boat 是判断 当前的 vehicles是不是Boat//(1) vehicles = null  : vehicles instanceof Boat  => false//(2) vehicles = 马对象 :vehicles instanceof Boat  => false//(3) vehicles = 船对象 :vehicles instanceof Boat  => trueif(!(vehicles instanceof Boat)){vehicles = VehiclesFactory.getBoat();}vehicles.work();}public void common(){//得到马/*没有进行vehicles判定,属实浪费了Horse horse = VehiclesFactory.getHorse();horse.work();*///(改进)判断一下,当前的 vehicles 属性是null, 就获取一匹马//(再次改进)if判断//if(vehicles == null){if(!(vehicles instanceof Horse)){//这里使用的是多态vehicles = VehiclesFactory.getHorse();}//这里体现使用接口调用vehicles.work();}//过火焰山public void passFireHill() {if (!(vehicles instanceof Plane)) {//这里使用的是多态vehicles = VehiclesFactory.getPlane();}//这里体现使用接口调用vehicles.work();}
}

测试类:

public class HomeWork04 {public static void main(String[] args) {Person tang = new Person("唐僧", new Horse());tang.common();tang.passRiver();//过火焰山tang.passFireHill();}
}
/*
1.有一个交通工具接口类Vehicles,有work接口
2.有Horse类和Boat类分别实现Vehicles
3.创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat
4.有Person类,有name和Vehicles属性,在构造器中为两个属性赋值
5.实例化Person对象“唐僧”,要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具
6.增加一个情况,如果唐僧过火焰山, 使用 飞机 ==> 程序扩展性, 我们前面的程序结构就非常好扩展 10min
使用代码实现上面的要求
编程 需求---->理解---->代码-->优化*/

第10章: 面向对象编程(高级部分)相关推荐

  1. 非零基础自学Java (老师:韩顺平) 第10章 面向对象编程(高级部分) 10.6 抽象类

    非零基础自学Java (老师:韩顺平) ✈[[零基础 快速学Java]韩顺平 零基础30天学会Java] 第10章 面向对象编程(高级部分) 文章目录 非零基础自学Java (老师:韩顺平) 第10章 ...

  2. Java_第08章_面向对象编程(高级)

    第08章_面向对象编程(高级) 本章专题与脉络 1. 关键字:static 回顾类中的实例变量(即非static的成员变量) class Circle{private double radius;pu ...

  3. 第7章 面向对象编程(OOP) 《Kotin 编程思想·实战》

    第7章 面向对象编程(OOP) 7.1 面向对象思想 7.2 类与继承 7.2.1 类 7.2.1.1 抽象类 7.2.1.2 接口 7.2.1.3 枚举 7.2.1.4 注解 7.2.1.5 静态类 ...

  4. Java面向对象编程(高级)

    面向对象编程(高级) 类变量和类方法 01: package ChildDemo;public class Child {private String name;public static int c ...

  5. week5 day4 面向对象编程高级

    week5 day4 面向对象编程高级 一. 判断是不是对象,是不是子类 二. 反射 2.1 反射机制的实现原理 2.2 四个内置函数的使用 2.3 用反射的好处 三. 内置方法 3.1 `__set ...

  6. Java基础学习——第六章 面向对象编程(下)

    Java基础学习--第六章 面向对象编程(下) 一.关键词:static 1. static关键字的引入 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new ...

  7. JavaSE:第5章 面向对象编程(中)

    文章目录 JavaSE:第5章 面向对象编程(中) 5.1 OOP特征二:继承性 5.2 方法的重写(override) 5.3 四种访问权限修饰符 5.4 关键字:super 5.5 子类对象实例化 ...

  8. 《PHP精粹:编写高效PHP代码》——第1章面向对象编程

    本节书摘来自华章社区<PHP精粹:编写高效PHP代码>一书中的第1章面向对象编程,作者:(美) Davey Shafik,更多章节内容可以访问云栖社区"华章社区"公众号 ...

  9. Matlab AppDesigner编程教程第1章——面向对象编程

    Matlab AppDesigner编程教程第1章--面向对象编程 文章目录 Matlab AppDesigner编程教程第1章--面向对象编程 前言 一.什么是面向对象编程 二.使用步骤 1.新建类 ...

  10. 《Kotin 极简教程》第7章 面向对象编程(OOP)(1)

    第7章 面向对象编程(OOP) 最新上架!!!< Kotlin极简教程> 陈光剑 (机械工业出版社) 可直接打开京东,淘宝,当当===> 搜索: Kotlin 极简教程 http:/ ...

最新文章

  1. 英特尔宣布全新自动驾驶平台整合处理器和视觉芯片
  2. linux cpu上下文切换 简介
  3. jvm优化_使用Java流和In-JVM-Memory的超低延迟查询
  4. Oracle数据类型简介【转贴】
  5. sqlite 数据量_向SQLite批量导入csv,txt数据
  6. HTML5 FileReader API 测试(一)
  7. oracle yum 本地源,Linux YUM本地源配置
  8. ios时间相差多少天,获取ios中两个日期之间的天数?
  9. 【android】AIDL传递自定义类型参数
  10. jenkins+testNG
  11. sde自动备份到文件gdb
  12. 视频教程-Dubbo视频教程-Java
  13. 2022年计算机一级考试网络安全素质教育模拟试题及答案
  14. Linux磁盘ext3变成ext4,从Ext3迁移到Ext4
  15. 广州恒义计算机科技,【长文】SONY MAP-S1解码一体机恒义科技HY-05台式耳放听感测评...
  16. PhpStorm 2019 for mac(PHP集成开发工具) 2019.1.3中文激活版
  17. REVIT模型空间句法分析
  18. 大数据分析师高级证书_大数据分析师(ACP)认证考试大纲
  19. pycharm跳出括号快捷键
  20. 中国市场开疆辟土,TokenRank与BiYong达成战略合作

热门文章

  1. UUIDUtil获取八位UUID
  2. Cocos Creator转盘抽奖
  3. Java面试题——Spring
  4. 深圳十大绝美看海圣地|深圳海边一日游攻略
  5. 给boss直聘的搜索结果加上hr活跃状态,少看点半年活跃的岗位,有书签版,油猴版
  6. X32汇编AAA,AAS,AAM,AAD,DAS,DAA
  7. RSA 非对称加密算法简述
  8. 音视频技术开发周刊 | 274
  9. 毕业设计-基于深度学习的数据融合方法研究
  10. C++中cv::Mat矩阵任意取其中一块或一行一列如何操作