java零基础Ⅱ-- 1.面向对象编程(高级)

java零基础Ⅱ-- 1.面向对象编程(高级)

连接视频



一、类变量和类方法

类变量 - 提出问题

提出问题的主要是目的就是让大家思考解决之道,从而引出要讲的知识点。

说:有一群小孩子在玩堆雪人,不时有新的小孩子加入,请问如何知道现在共有多少人在玩?编写程序解决。


传统的方法解决

使用我们现有的技术来解决这个问题,大家看看如何?

思路:
1、在main方法中定义一个变量 count
2、当一个小孩子加入游戏后 count++ ,最后 count 就记录有多少个小孩子在玩游戏

package com.zzpedu.static_;

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 + " 加入了游戏...");
    }
}
==========控制台输出=============
白骨精 加入了游戏...
狐狸精 加入了游戏...
老鼠精 加入了游戏...
共有 3 小孩子加入了游戏...

问题分析:

1、count 是一个独立对象/类变量,很尴尬
2、以后我们访问 count 很麻烦,没有使用到OOP
3、因此,我们因此 类变量/静态变量


类变量快速入门

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

package com.zzpedu.static_;

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("共有 " + Child.count + " 小孩子加入了游戏...");
        System.out.println("child1.count=" + child1.count);//3
        System.out.println("child2.count=" + child2.count);//3
        System.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 + " 加入了游戏...");
    }
}
==========控制台输出=============
白骨精 加入了游戏...
狐狸精 加入了游戏...
老鼠精 加入了游戏...
共有 3 小孩子加入了游戏...
child1.count=3
child2.count=3
child3.count=3

类变量内存布局

画一个小图给大家理解。 ==> 画出一个示意图帮助理解,静态变量放在哪里?

在这里插入图片描述

https://blog.csdn.net/x_iya/article/details/81260154/

https://www.zhihu.com/question/59174759/answer/163207831

有效书说在方法区…, jdk 版本有关系,记住一点: static 变量是对象共享

不管static 变量,在哪里,
共识(1)static 变量是同一个类所有对象共享 (2)static类变量,在类加载的时候就生成。


什么是类变量

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


如何定义类变量

定义语法:

访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名;


如何访问类的变量

类名.类的变量名【说明:类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问】
或者
对象名.类变量名【静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的】
推荐使用:类名.类变量名

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 = "zzp先生";
}
==========控制台输出=============
zzp先生
a.name=zzp先生

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

1、什么时候需要使用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计使用学生共交多少钱。

Student (name, static fee)

2、类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象的独享的。

3、加上 static 称为类变量或者静态变量,否则称为实例变量/普通变量/非静态变量

class A{
    //类变量
    //类变量的访问,必须遵守 相关的访问权限
    public static String name = "zzp先生";
    //实例变量/普通成员变量/普通属性/非静态属性/非静态成员变量
    private int num = 10;
}

4、类变量可以通过 类名.类变量名 或者 对象名.类变量名 类访问,但java设计者推荐我们使用 类名.类变量名 方式访问 。【前提是 满足访问修饰符的访问权限和范围

5、实例变量不能通过 类名.类变量名 方式访问

6、类变量是在类加载就初始化 ,也就是说,即使你没有创建对象,只有类加载了,就可以使用类变量了。

7、类变量的生命周期是随着类的加载开始,随着类消亡而销毁。

public class StaticDetail {
    public static void main(String[] args) {
        B b = new B();
        //System.out.println(B.n1);//实例变量不能通过 类名.类变量名 方式访问
        System.out.println(B.n2);//200

        //静态变量 是类加载的时候,就创建了,所以我们没有创建对象实例
        //也可以通过 类名.类变量名 来访问
        System.out.println(C.address);//中国
    }
}
class B{
    //实例变量
    public int n1 = 100;
    //静态变量
    public static int n2 = 200;
}
class C{
    //静态变量
    public static String address = "中国";
}

类的方法基本介绍

类方法也叫静态方法。
形式如下:

访问修饰符 static 数据返回类型 方法名(){ }【推荐】
static 访问修饰符 数据返回类型 方法名(){ }


类方法的调用

使用方式:

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


类方法应用案例

请大家看一个静态方式小案例。(统计学费总和)

package com.zzpedu.static_;

public class StaticMethod {
    public static void main(String[] args) {
        Stu tom = new Stu("tom");
        //tom.payFee(100);
        Stu.payFee(100);
        tom.showFee();

        Stu mary = new Stu("mary");
        //mary.payFee(200);
        Stu.payFee(200);
        mary.showFee();

        //输出当前收到的总学费
        System.out.println("------");
        Stu.showFee();
    }
}
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);
    }
}
==========控制台输出=============
总学费有:100.0
总学费有:300.0
------
总学费有:300.0

类方法经典的使用场景

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

如果我们希望不创建实例(对象),也可以调用某个(即当做工具来使用),这时,把方法做成静态方法非常合适

比如:工具类中的方法 utils
Math类、Arrays类、Collections 集合类看下源码

小结:

在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这有我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务 等

//开发自己的工具类,可以将方法静态的,方便调用
class MyTools{
    //求出两个数之和
    public double calSum(double n1, double n2){
        return n1 + n2;
    }
    //可以写出很多这样的工具方法...
}

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

1)类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区上:

类方法中无this的参数
普通方法中隐含着this的参数

2)类方法可以通过类名调用,也可以通过对象调用

3)普通方法和对象有关,需要通过对象名调用,比如:对象名.方法名(参数),不能通过类名调用

4)类方法中不允许使用和对象相关的关键字,比如:this和super。普通方法(成员方法)可以。

5)类方法(静态方法)中 只能访问 静态变量 或 静态方法
口诀:静态方法只能方法静态成员/方法

6)普通成员方法,即可以访问 非静态的成员,也可以访问静态成员。

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

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi();//ok
        //非静态方法,不能通过类名调用
        //D.say();//错误,需要选创建对象,在调用
        D d = new D();
        d.say();
    }
}
class D{
    private int n1 = 100;
    private static int n2 = 200;
    public void say(){//非静态方法,普通方法

    }
    public static void hi(){//静态方法,类方法
        //类方法中不允许使用和对象相关的关键字,
        // 比如:this和super。普通方法(成员方法)可以
        //super();//错误
        //this.n1; //错误
    }
    //类方法(静态方法)中 只能访问 静态变量 或 静态方法
    //口诀:静态方法只能方法静态成员/方法
    public static void hello(){//静态方法,类方法
        System.out.println(n2);
        System.out.println(D.n2);
        //System.out.println(this.n2);//错误 不能使用
        hi();
        //say();//错误 不能使用
    }
    //普通成员方法,即可以访问 非静态的成员,也可以访问静态成员。
    //小结:非静态方法可以访问 静态成员和非静态成员
    public void ok(){
        //非静态的成员
        System.out.println(n1);
        say();
        //静态成员
        System.out.println(n2);
        hello();
    }
}

类方法练习

1、下面输出什么?

public class Test{
	static int count = 9;
	public void count(){
		System.out.println("count=" + (count++));//后++  先输出 后自增
	}
	public static void main(String[ ] args) {
		new Test().count();//9
		new Test().count();//10
		System.out.println(Test.count);//11
	}
}

2、看看下面代码有没有错误,如果有错误,就修改,看看输出什么?

public class TestPerson {
    public static void main(String[] args) {
        System.out.println("Number of total is " + Person.getTotalPerson());// 0
        Person p1 = new Person();//创建对象 执行构造器 --》total++ --》 id = total
        System.out.println("Number of total is " + Person.getTotalPerson());// 1
    }
}
class Person{
    private int id;
    private static int total = 0; //初始化 0
    public static int getTotalPerson(){
        //id++;//错误 静态方法不可以访问非静态成员
        return total;
    }
    public Person(){//构造器
        total++;//普通方法 可以访问静态属性 total=1
        id = total;// id = 1
    }
}
===========控制台输出===========
Number of total is 0
Number of total is 1

3、看看下面代码有没有错误,如果有错误,就修改,看看total等于多少? = 4

public class TestPerson {
    public static void main(String[] args) {
        Person.setTotalPerson(3);
        new Person();//最后 total的值就是 4
    }
}
class Person{
    private int id;
    private static int total = 0; //初始化 0

    public static void setTotalPerson(int total){
        //this.total = total;//错误 静态方法不能使用this关键字
        Person.total = total;
    }
    public Person(){//构造器
        total++;
        id = total;
    }
}

小结:
(1)静态方法只能访问静态成员【属性、方法】
(2)非静态方法,可以访问使用成员
(3)在编写代码时,仍然要遵守访问权限规则



二、理解main方法语法 static

深入理解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

在这里插入图片描述

public class Hello {
	public static void main(String[] args) {
        //args 是如何传入的
        //遍历显示
        for(int i = 0; i < args.length; i++){
        	System.out.println("第" + (i + 1) + "个参数=" + args[i]);
        }
    }
}

在这里插入图片描述

特别提示:

1)在main()方法找中,我们可以直接调用main方法所在类的静态方法或静态属性
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员

package com.zzpedu.main_;

public class Main01 {
    //静态的变量/属性
    private static String name = "zzp先生";
    //非静态的变量/属性
    private int n1 = 100;

    //静态方法
    public static void hi(){
        System.out.println("Main01的 hi方法");
    }
    //非静态方法
    public static void cry(){
        System.out.println("Main01的 cry方法");
    }

    public static void main(String[] args) {
        //可以直接使用 name
        //1.静态方法main 可以访问本类的静态成员
        System.out.println("name=" + name);//zzp先生
        hi();//Main01的 hi方法
        //2.静态方法main 不可以访问本类的非静态成员
        //System.out.println("n1=" + n1);//错误
        //cry();//错误
        //3.静态方法main 要访问本类的非静态成员,需要先创建对象,再调用即可
        Main01 main01 = new Main01();
        System.out.println(main01.n1);//100
        main01.cry();//Main01的 cry方法
    }
}
============控制台输出==========
name=zzp先生
Main01的 hi方法
100
Main01的 cry方法

案例演示

public class CommandPara{
	public static void main(String[] args){
		for(int i = 0; i < args.length; i++){
			System.out.println("args[" + i + "] = " + args[i]);
		}
	}
}
//运行程序
java CommandPara "lisa" "bily" "Mr Brown"

说明:在idea如何传递参数

在这里插入图片描述

在这里插入图片描述
运行
在这里插入图片描述



三、代码块

基本介绍

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

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


基本语法

[修饰符] {
	代码
};

说明注意:
1)修饰符 可选,要写得话,也只能写 static

2)代码块可分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块/非静态代码块

3)逻辑语句可以以为任何逻辑语句(输入、输出、方法调用、循环、判断等)

4); 号可以写上,也可以省略。


代码块的好处和案例演示

理解:
1)相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作

2)场景:如果多个构造器中都有重复的语句,可以抽取到初始化代码块中,提高代码的重用性

package com.zzpedu.codeblock_;

public class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie1 = new Movie("明日之战");
        System.out.println("---------------------");
        Movie movie2 = new Movie("失控玩家",50,"肖恩·利维");
    }
}
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) 构造器被调用了...");
//        System.out.println("电影屏幕打开了...");
//        System.out.println("广告开始了...");
//        System.out.println("电影正式开始了...");
        this.name = name;
    }
    public Movie(String name, double price) {
//        System.out.println("电影屏幕打开了...");
//        System.out.println("广告开始了...");
//        System.out.println("电影正式开始了...");
        this.name = name;
        this.price = price;
    }
    public Movie(String name, double price, String director) {
        System.out.println("Movie(String name, double price, String director) 构造器被调用了...");
//        System.out.println("电影屏幕打开了...");
//        System.out.println("广告开始了...");
//        System.out.println("电影正式开始了...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}
==============控制台输出================
电影屏幕打开了...
广告开始了...
电影正式开始了...
Movie(String name) 构造器被调用了...
---------------------
电影屏幕打开了...
广告开始了...
电影正式开始了...
Movie(String name, double price, String director) 构造器被调用了...

代码块使用注意事项和细节讨论

1)static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行

2)类什么时候被加载

2.1 创建对象实例时(new)
2.2 创建子类对象实例,父类也会被加载,而且,父类先被加载,子类后被加载
2.3 使用类的静态成员时(静态属性,静态方法)

案例演示:A 类 extends B 类 的静态块

3)普通的代码块,才创建对象实例时,会被隐式的调用。

被创建一次,就调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。

package com.zzpedu.codeblock_;

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        //类被加载的情况举例
        //1. 创建对象实例时(new)
        //AA aa = new AA();//AA 的静态代码块1被执行...
        //2. 创建子类对象实例,父类也会被加载,而且,父类先被加载,子类后被加载
        AA aa = new AA();
        System.out.println("-------------");
        //3. 使用类的静态成员时(静态属性,静态方法)
        System.out.println(Cat.n1);
        System.out.println("-------------");
        //static 静态代码块 是在类加载时,执行时,而且只会执行一次
        //普通的代码块,才创建对象实例时,会被隐式的调用。
        //被创建一次,就调用一次。
//        DD dd = new DD();
//        DD dd1 = new DD();
        System.out.println("-------------");
        //如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(DD.n1);//888 静态代码块先被调用

    }
}
class DD{
    public static int n1 = 888;//静态属性
    //静态代码块
    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 = 100;
    //静态代码块
    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
    }
}
=========控制台输出============
BB 的静态代码块1被执行...
AA 的静态代码块1被执行...
-------------
Animal 的静态代码块1被执行...
Cat 的静态代码块1被执行...
100
-------------
-------------
DD 的静态代码块1被执行...
888

小结:

  1. static 代码块是类加载时,执行,只会执行一次
  2. 普通代码块是在创建对象时调用的,创建一次,调用一次

4)创建一个对象时,在一个类 调用顺序是

4.1 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)

4.2 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)

4.3 调用构造方法。

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        //(1) A 静态代码块01 (2) getN1被调用
        //(3) getN2被调用  (4) A 普通代码块01
        //(5) A() 无参构造器被调用
        A a = new A();
    }
}
class A{

    private int n2 = getN2();//普通属性的初始化
    {//普通代码块
        System.out.println("A 普通代码块01....");
    }

    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 200;
    }
    public A(){//无参构造器
    System.out.println("A() 无参构造器被调用....");
    }
}
========控制台输出======
A 静态代码块01....
getN1被调用...
getN2被调用...
A 普通代码块01....
A() 无参构造器被调用....

5)构造器 的最前面其实隐含了 super() 和 调用普通代码块。

新写一个类演示,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于 构造器和普通代码块执行的

class A {
	public A{//构造器
		//这里有隐藏的执行要求
		//(1) super();
		//(2) 调用普通的代码块的
		System.out.println("ok");
	}
}
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() 构造器被调用...");
    }
}
=============控制台输出===============
AAA的普通代码块...
AAA() 构造器被调用...
BBB的普通代码块...
BBB() 构造器被调用...

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

6.1 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)

6.2 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)

6.3 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

6.4 父类的构造方法

6.5 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

6.6 子类的构造方法

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        //说明:
        //(1) 进行类的加载
        // 1.1 先加载 父类 A02
        // 1.2 再加载 B02
        //(2) 创建对象
        // 2.1 从子类的构造器开始
        // 2.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 static 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 static int getVal04(){
        System.out.println("getVal04()执行...");//(8)
        return 10;
    }
    //
    public B02(){//构造器
        //隐藏了
        //super();
        //普通代码块和普通属性的初始化...
        System.out.println("B02的构造器...");//(10)
    }
}
============控制台输出===========
getVal01()执行...
A02的第一个静态代码块...
getVal03()执行...
B02的第一个静态代码块...
A02的第一个普通代码块...
getVal02()执行...
A02的构造器...
getVal04()执行...
B02的第一个普通代码块...
B02的构造器...

7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

public class CodeBlockDetail05 {
    public static void main(String[] args) {
        new C02();
    }
}

class C02{
    private int n1 = 100;
    private static int n2 = 200;
    private void m1(){
		System.out.println("m1()普通方法被调用");
    }
    private static void m2(){
		System.out.println("m2()静态方法被调用");
    }
    static {
        //静态代码块,只能调用静态成员
        //System.out.println(n1);//错误
        System.out.println(n2);
        //m1();//错误
        m2();
    }
    {
        //普通代码块,可以使用任意成员
        System.out.println(n1);
        System.out.println(n2);
        m1();
        m2();
    }
}
============控制台输出===========
200
m2()静态方法被调用
100
200
m1()普通方法被调用
m2()静态方法被调用

练习

1、下面的代码输出什么?

class Person{
	public static int total;//静态变量
	static{//静态代码块
		total = 100;
		System.out.println("in static block!");//1
	}
}
public class Test{
	public static void main(String[] args){
		System.out.println("total = " + Person.total);//in static block! total = 100
		System.out.println("total = " + Person.total);//total = 100
	}
}
public class CodeBlockExercise01 {
    public static void main(String[] args){
        System.out.println("total = " + Person.total);//in static block! total = 100
        System.out.println("total = " + Person.total);//total = 100
    }
}
class Person{
    public static int total;//静态变量
    static{//静态代码块
        total = 100;
        System.out.println("in static block!");//1
    }
}
============控制台输出===========
in static block!
total = 100
total = 100

2、下面的代码输出什么?

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



四、单例设计模式

什么是设计模式

1、静态方法和属性的经典使用

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


什么是单例模式

单例(单个的实例)

1、所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法

2、单例模式有两种方法:
1)饿汉式
2)懒汉式


单例模式引用实例

演示 饿汉式 和 懒汉式 单例模式的实现。

步骤如下:

1)构造器私有化 ⇒ 防止直接 new 对象

2)类的内部创建对象

3)向外暴露一个静态的公共方法。getInstance

4)代码实现

饿汉式演示

package com.zzpedu.single_;

/**
 * 饿汉式演示
 */
public class SingleTon01 {
    public static void main(String[] args) {
        System.out.println(GirlFriend.n1);
        //通过方法可以获取对象
        GirlFriend instance1 = GirlFriend.getInstance();
        System.out.println(instance1);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);

        System.out.println(instance1 == instance2);//true
    }
}
//有一个类,GirlFriend
//只能有一个 GirlFriend
class GirlFriend{
    private String name;
    public static int n1 = 100;
    //为了能够静态方法中,返回 gf对象,需要将其修饰为static
    //静态 只能初始化一次
    //对象,通常是重量级的对象 类加载时就创建了
    //饿汉式模式可能造成创建了对象,但是没有使用
    private static GirlFriend gf = new GirlFriend("小红");
    //如何保障我们只能创建一个 GirlFriend
    //步骤:[单例模式--饿汉式]
    //1. 将构造器私有化
    //2. 在类的内部直接创建对象(改对象是静态的static)
    //3. 提供一个公共的static方法
    private GirlFriend(String name) {
        System.out.println("构造器被调用...");
        this.name = name;
    }
    public static GirlFriend getInstance(){
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}
=========控制台输出========
构造器被调用...
100
GirlFriend{name='小红'}
GirlFriend{name='小红'}
true

懒汉式演示

package com.zzpedu.single_;

/**
 * 懒汉式演示
 */
public class SingleTon02 {
    public static void main(String[] args) {
        System.out.println(Cat.n1);
        Cat instance = Cat.getInstance();
        System.out.println(instance);

        //再次调用getInstance
        Cat instance2 = Cat.getInstance();
        System.out.println(instance2);

        System.out.println(instance == instance2);//true
    }
}
//希望在程序运行过程中,只能创建一个Cat
//使用单例模式
class Cat{
    private String name;
    public static int n1 = 999;
    private static Cat cat;//默认为null
    //步骤:
    //1. 仍然将构造器私有化
    //2. 定义一个static静态属性对象
    //3. 提供一个public的static方法,可以返回一个Cat对象
    //4. 懒汉式,只有当用户使用getInstance时,才返回cat对象,后面再次调用时,会上次创建的对象
    //  从而保证了单例
    private Cat(String name){
        System.out.println("构造器被调用...");
        this.name = name;
    }
    public static Cat getInstance(){
        if(null == cat){//如果没有创建cat对象
            cat = new Cat("小花");
        }
        return cat;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
=========控制台输出========
999
构造器被调用...
Cat{name='小花'}
Cat{name='小花'}
true

饿汉式 VS 懒汉式

  1. 二者最主要的区别在于创建对象的 时机 不同;饿汉式是在类加载就创建了对象实例,而 懒汉式 是在使用时才创建

  2. 饿汉式 不存在线程安全问题,懒汉式存在线程安全问题。

  3. 饿汉式 存在浪费资源的可能。因为如果是程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。

  4. 在我们javaSE标准类中,java.lang.Runtime 就是经典的单例模式。

在这里插入图片描述

小结:

1、单例模式的两种实现方式:(1)饿汉式(2)懒汉式
2、饿汉式的问题:在类加载时候就创建,可能存在资源浪费问题
3、懒汉式的问题:线程安全问题



五、final 关键字


基本介绍

final 中文意思:最后的,最终的

final 可以修饰类、属性、方法和局部变量。

在某些情况下,程序员可能有以下需求,就会使用到 final:

1)当不希望类被继承时,可以使用final修饰。

2)当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。

演示:访问修饰符 final 返回类型 方法名

3)当不希望类的某个属性的值被修改时,可以使用final修饰

演示:public final double TAX_RATE = 0.08;

4)当不希望某个局部变量被修改时,可以使用final修饰

final double TAX_RATE = 0.08;

package com.zzpedu.final_;

public class Final01 {
    public static void main(String[] args) {
        E e = new E();
        //e.TAX_RATE = 0.09;//报错
    }
}
//如果我们要求A类不能被其它类继承
//可以使用final修饰 A类
final class A{

}
//class B extends A{ }//报错
class C{
    //如果我们要求hi方法不能被子类重写
    //可以使用final修饰 hi方法
    public final void hi(){}
}
class D extends C{
//    @Override
//    public void hi() {//报错
//        System.out.println("重写了C类的hi方法...");
//    }
}
//当不希望类的某个属性的值被修改时,可以使用final修饰
class E{
    public final double TAX_RATE = 0.08;
}
//当不希望某个局部变量被修改时,可以使用final修饰
class F{
    public void cry(){
        //这时,NUM 也称为 局部常量
        final double NUM = 0.01;
        //NUM = 0.9;//报错
        System.out.println("NUM=" + NUM);
    }
}

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方法,则该方法虽然不能重写,但可以被继承。

public class FinalDetail01 {
    public static void main(String[] args) {
        CC cc = new CC();

        new EE().cal();//输出:cal()方法
    }
}
class AA{
    //1. 定义时:如 public final double TAX_RATE = 0.08;
    //2. 在构造器中
    //3. 在代码块中
    public final double TAX_RATE = 0.08;//1. 定义时 赋值
    public final double TAX_RATE2;
    public final double TAX_RATE3;

    public AA() {//2. 在构造器中 赋值
        TAX_RATE2 = 1.1;
    }
    {//3. 在代码块中 赋值
        TAX_RATE3 = 8.8;
    }
}
class BB{
    /*
    如果final修饰的属性是静态的,则初始化的位置只能是
    1.定义时
    2.在静态代码块 不能在构造器中赋值
    */
    public static final double TAX_RATE = 99.99;//1. 定义时
    public static final double TAX_RATE2;
    //public static final double TAX_RATE3;
//    public BB(){//2. 不能在构造器中赋值 因为构造器不在类加载的时候赋值
//        TAX_RATE3 = 8.8;
//    }
    static {//2. 在静态代码块中赋值
        TAX_RATE2 = 3.3;
    }
}
//final类不能继承,但是可以实例化对象
final class CC{ }
//如果类不是final类,但是含有final方法,则该方法虽然不能重写,但可以被继承
//即,仍然遵守继承的机制
class DD{
    public final void cal(){
        System.out.println("cal()方法");
    }
}
class EE extends DD{ }

6)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。

7)final不能修饰构造器方法(即构造器)

8)final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。

9)包装类(Integer、Double、Float、Boolean等都是final),String也是final类。

在这里插入图片描述
在这里插入图片描述

public class FinalDetail02 {
    public static void main(String[] args) {
        System.out.println(BBB.NUM);//10000 静态代码块不会执行
    }
}
//final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。
class BBB{
    //final 和 static 可以颠倒
    public final static int NUM = 10000;
    static{
        System.out.println("BBB 静态代码块执行了...");
    }
}
final class AAA{
    //一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
    //public final void cry(){ }
    public void cry(){ }
}

final应用实例

题1

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

public class FinalExercise01 {
    public static void main(String[] args) {
        Circle circle = new Circle(5.0);
        System.out.println("面积=" + circle.calArea());//面积=78.5
    }
}
class Circle{
    private double radius;//半径
    //private final double PI = 3.14;//第1种 推荐
    private final double PI;
    //构造器
    public Circle(double radius) {
        this.radius = radius;
       // PI = 3.14;//第2种
    }
    {//普通代码块
        PI = 3.14;//第3种
    }
    public double calArea(){
        return PI * radius * radius;
    }
}

题2

public class Something{//下面代码有误,为什么?
	public int addOne(final int x){
		++x;//错误,原因是不能修改 final x的值
		return x + 1;//这里是可以的
	}
}



六、抽象类


先看一个问题

一个小问题,还是看个程序

class Animal{
	private String name;
	private int age;
	public Animal(String name, int age){
		super();
		this.name = name;
		this.age = age;
	}
	//动物都有eat的行为
	public void eat(){
		System.out.println("这是一个动物,但是目前不知道吃什么");
	}
}

父类方法不确定性

小结:

当父类的某些方法,需要声明,但是有又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类

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();//抽象方法
}

抽象类的介绍

1)用 abstract 关键字来修饰一个类时,这个类就叫抽象类

访问修饰符 abstract 类名{ } 

2)用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法

访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体

3)抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类

4)抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多


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

1)抽象类不能被实例化

2)抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有abstract方法,还可以有实现的方法

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

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

package com.zzpedu.abstract_;

public class AbstractDetail01 {
    public static void main(String[] args) {
        //1. 抽象类不能被实例化
       // new A();//报错
    }
}
//2. 抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有abstract方法
//,还可以有实现的方法
abstract class A{
    public void hi(){
        System.out.println("hi");
    }
}
//3. 一旦类包含了abstract方法,则这个类必须声明为abstract
abstract class B{
    public abstract void hi();
}
//4. abstract 只能修饰类和方法,不能修饰属性和其他的。
class C{
    //public abstract int n1 = 1;//报错
}

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

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

abstract void aaa(){ };//错误,不能有{ }

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

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

class Father{
	public static void method(){
		System.out.println("父类的方法");
	}
}
class Son exends Father{
	public static void method(){
		System.out.println("子类的方法");
	}
}
//抽象方法不能使用private、final 和 static 来修饰,因为这些关键字都是和重写相违背的
abstract class H{
    public abstract void hi();//抽象方法
    //private abstract void OK();//报错
    //public final abstract void OK();//报错
    //static abstract void OK();//报错
}

//如果一个类继承了抽象类,则它必须实现抽象类的使用抽象方法,除非自己也声明为abstract类
abstract class E{
    public abstract void hi();
}
abstract class F extends E{ }
class G extends F{
    @Override
    public void hi() {//这里相等于G子类实现了父类E的抽象方法,所谓实现方法,就是有方法体

    }
}

// 抽象类本质还是类,所以可以有类的各种成员
abstract class D{
    public int n1 = 10;
    public static String name = "zzp先生";
    public void hi(){
        System.out.println("hi");
    }
    public abstract void hello();
    public static void ok(){
        System.out.println("ok");
    }
}

练习题

1)题1:思考:abstract final class A{} 能编译通过吗?为什么

错误。final 修饰的类不能被继承,因为abstract 类需要被子类继承

2)题2:思考:abstract public static void test02(); 能编译通过吗?为什么

错误。static 关键字和重写无关 ,因为abstract 方法需要被重写

3)题3:思考:abstract private void test03(); 能编译通过吗?为什么

错误。private 的方法不能重写,因为abstract 方法需要被重写

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

package com.zzpedu.abstract_;

public 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;
    }

    //将work做成一个 抽象方法
    public abstract void work();

    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 class Manager extends Employee{
    //特有属性
    private double bonus;

    public Manager(String name, int id, double salary) {
        super(name, id, salary);
    }

    @Override
    public void work() {
        System.out.println("经理 " + getName() + " 工作中...");
    }

    public double getBonus() { return bonus; }
    public void setBonus(double bonus) { this.bonus = bonus; }
}
public class CommonEmployee extends Employee{
    public CommonEmployee(String name, int id, double salary) {
        super(name, id, salary);
    }
    @Override
    public void work() {
        System.out.println("普通员工 " + getName() + " 工作中...");
    }
}
public class AbstractExercise01 {
    public static void main(String[] args) {
        //测试
        Manager jack = new Manager("jack", 998, 50000);
        jack.setBonus(8000);
        jack.work();

        CommonEmployee commonEmployee = new CommonEmployee("tom",888,20000);
        commonEmployee.work();
    }
}
==========控制台输出=========
经理 jack 工作中...
普通员工 tom 工作中...



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

最佳实践

需求:

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

感情的自然流露:
1、先用最容易想到的方法 -》 代码实现
2、分析问题,提出使用模板设计模式

设计一个抽象类(Template),能完成如下功能:
1)编写一个方法calculateTime(),可以计算某段代码的耗时时间
2)编写抽象方法job();
3)编写一个子类Sub,继承抽象类Template,并实现job方法
4)编写一个测试类TestTemplate,看看是否好用。

package com.zzpedu.abstract_;

public abstract class Template {//抽象类 - 模板设计模式

    public abstract void job();//抽象方法

    public void calculateTime(){//实现方法,调用job方法
        //得到开始时间
        long start = System.currentTimeMillis();
        job();//动态绑定机制
        //得到结束的时间
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间(单位毫秒)=" + (end - start) );
    }
}
public class AA extends Template{
    //计算任务
    //1+..+800000
    @Override
    public void job(){//这里重写的父类方法
        long num = 0;
        for (long i = 1; i <= 800000; i++) {
            num += i;
        }
    }
}
public class BB extends Template{
    @Override
    public void job(){//这里重写的父类方法
        long num = 0;
        for (long i = 1; i <= 80000; i++) {
            num *= i;
        }
    }
}
public class TestTemplate {
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();//多态,动态绑定机制

        BB bb = new BB();
        bb.calculateTime();
    }
}



七、接口


为什么有接口

请求大家看一张图:
在这里插入图片描述

usb插槽就是实现中的接口

你可以把手机,相机,u盘都插在sub插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等。


接口快速入门

这样的设计需求在java编程 /php/.net/go 中也是会大量存在的。我曾经说过,一个程序就是一个世界,在现实世界存在的情况,在程序也会出现。我们用程序来模拟一下。

package com.zzpedu.interface_;

public interface UsbInterface {//接口
    //规定接口的相关方法,即 规范
    public void start();
    public void stop();
}
//Phone 类 实现 UsbInterface接口
//1. 即Phone类需要实现 UsbInterface接口 规定/声明的方法
public class Phone implements UsbInterface{
    @Override
    public void start() {
        System.out.println("手机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("手机停止工作...");
    }
}
public class Camera implements UsbInterface{//实现接口,就是把接口的方法实现
    @Override
    public void start() {
        System.out.println("相机开始工作...");
    }
    @Override
    public void stop() {
        System.out.println("相机停止工作...");
    }
}
public class Interface01 {
    public static void main(String[] args) {
        //创建手机,相机对象
        Camera camera = new Camera();
        Phone phone = new Phone();
        Computer computer = new Computer();
        computer.work(phone);//那手机接入到计算机
        System.out.println("--------------");
        computer.work(camera);//那相机接入到计算机
    }
}
======控制台输出=========
手机开始工作...
手机停止工作...
--------------
相机开始工作...
相机停止工作...

基本介绍

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

interface 接口名{
	//属性
	//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}

class 类名 implements 接口{
	//自己属性
	//自己方法
	必须实现的接口的抽象方法
}

小结:

1、在 jdk7.0 前 接口里面的所有方法都没有方法体,即都是抽象方法
2、jdk8.0 后 接口可以有静态方法(但是需要使用default关键字修饰),默认方法,也就是说接口中可以有方法的具体实现

package com.zzpedu.interface_;

public interface AInterface {
    //写属性
    public int n1 = 10;
    //写方法
    //在接口中,抽象方法,可以省略abstract关键字
    public void hi();
    //在jkd8后,可以默认实现方法,但是需要使用default关键字修饰
    default void ok(){
        System.out.println("ok()...");
    }
    //在jkd8后, 可以静态方法
    public static void cry(){
        System.out.println("cry()...");
    }
}
//解读
//1.如果一个类 implements实现 接口
//2.需要将该接口的所有抽象方法都实现
class A implements AInterface{

    @Override
    public void hi() {
        System.out.println("hi()...");
    }
}

深入讨论

对初学者讲,理解接口的概念不算太难,难的是不知道什么时候使用接口,下面列举几个应用场景:

1、说现在要制造战斗机,武装直升机,专家只需要的功能/规格定下来即可,然后让别的人具体实现即可。

2、说现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后有程序员具体实现。

实际要求:3个程序员,编写三个类,分别完成对Mysql,Oracle,DB2数据库的连接 connect close
在这里插入图片描述

package com.zzpedu.interface_;

public interface DBInterface {//项目经理定义
    public void connect();//连接方法
    public void close();//关闭连接
}
//A程序员连接mysql
public class MysqlDB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接mysql");
    }
    @Override
    public void close() {
        System.out.println("关闭mysql");
    }
}
//B程序员连接Oracle
public class OracleDB implements DBInterface{
    @Override
    public void connect() {
        System.out.println("连接oracle");
    }
    @Override
    public 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);
    }
    public static void t(DBInterface db){
        db.connect();
        db.close();
    }
}
============控制台输出===================
连接mysql
关闭mysql
连接oracle
连接oracle

注意事项和细节

1)接口不能被实例化

2)接口中所以方法是 public 方法,接口中的抽象方法,可以不用abstract 修饰

void add();
实际上是 abstract void add();
void add(){ };//错误

3)一个普通类实现接口,就必须将该接口的所有方法都实现

4)抽象类实现接口,可以不用实现接口的方法。

package com.zzpedu.interface_;

public class InterfaceDetail01 {
    public static void main(String[] args) {
        //接口不能被实例化
       // new IA();//报错
    }
}
//1.接口不能被实例化
//2.接口中所以方法是 public 方法,接口中的抽象方法,可以不用abstract 修饰
//3.一个普通类实现接口,就必须将该接口的所有方法都实现,(将光标放在类上,使用 alt + enter 快捷键来解决)
//4.抽象类实现接口,可以不用实现接口的方法
interface IA{
    void say();//等价于==》public abstract void say();
    void hi();//修饰符 public protected 默认 private
}
class Cat implements IA{
    @Override
    public void say() {

    }
    @Override
    public void hi() {

    }
}
//4.抽象类实现接口,可以不用实现接口的方法
abstract class Tiger implements IA{

}

5)一个类同时可以实现多个接口

6)接口中的属性,只能是final的,而且是 public static final 修饰符:
比如:

int a = 1; 实际上是 public static final int a =1;(必须初始化)

7)接口中属性的访问形式:接口名.属性名

8)接口不能继承其它类,但是可以继承多个别的接口

interface A extends B,C{ }

9)接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的。

public class InterfaceDetail02 {
    public static void main(String[] args) {
        //接口中的属性,是 public static final
        System.out.println(IB.a);//1 说明a就是static静态属性
        //IB.a = 10;//报错 不能修改因为a是 final属性
    }
}
interface IB{
    //6.接口中的属性,只能是final的,而且是 public static final 修饰符
    int a = 1;//等价于==》public static final int a = 1;//(必须初始化)
    void hi();
}
interface IC{
    void say();
}
//8.接口不能继承其它类,但是可以继承多个别的接口
interface ID extends IB,IC{

}
//9.接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的。
interface IE{ }
//5.一个类同时可以实现多个接口
class Pig implements IB,IC{
    @Override
    public void hi() {
    }
    @Override
    public void say() {
    }
}

练习

1、

interface A{
	int a = 23;//等价于==》public static final int a = 23;
}
class B implements A{
}
在main中
B b = new B();//ok
System.out.println(b.a);//23
System.out.println(A.a);//23
System.out.println(B.a);//23

语法是否正确,如果正确,输出什么?

public class InterfaceExercise01 {
    public static void main(String[] args) {
        B b = new B();//ok
        System.out.println(b.a);//23
        System.out.println(A.a);//23
        System.out.println(B.a);//23
    }
}
interface A{
    int a = 23;//等价于==》public static final int a = 23;
}
class B implements A{ }
========控制台输出======
23
23
23

实现接口 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){
        super();
        this.name = name;
    }
    public void climbing(){
        System.out.println(name + " 会爬树...");
    }

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

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

继承的价值主要在于:解决代码的复用性和可维护性

接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活…

接口比继承更加灵活

接口比继承更加灵活,继承是满足 is - a 的关系,而接口是需要满足 like - a 的关系。

接口在一定程度上实现代码解耦【即:接口规范性 + 动态绑定】


接口的多态特性

1)多态参数
在前面的Usb接口案例,UsbInterface usb,即可以接收手机对象,有可以接收相机对象,就体现了 接口多态(接口引用可以指向实现了接口的类对象)

public class Computer {
    //编写一个方法,计算机工作
    //解读:
    //1. UsbInterface usbInterface 形参是接口类型
    //2. 看到 接收 实现了 UsbInterface接口的类的对象实例
    public void work(UsbInterface usbInterface){
        //通过接口,来调用方法
        usbInterface.start();
        usbInterface.stop();
    }
}
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{}

2)多态数组
演示一个案例:给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("手机可以打电话...");
    }
    @Override
    public void work() {
        System.out.println("手机工作中...");
    }
}
class Camera_ implements Usb{
    @Override
    public void work() {
        System.out.println("相机工作中...");
    }
}
=========控制台输出======
手机工作中...
手机可以打电话...
相机工作中...

3)接口存在多态传递现像

/**
 * 演示接口存在多态传递现像
 */
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 {
    @Override
    public void hi() {
    }
}

接口练习

interface A{
    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.x
        System.out.println(A.x + " " + super.x);
    }
    public static void main(String[] args) {
        new C().pX();//0 1
    }
}

代码有没错误,有错误就改,改好后,看输出什么?



八、内部类


基本介绍

一个类的内部类又完整的嵌套了另一个类结构。被嵌套的类称为 内部类(inner class),嵌套其他类的类称为外部类(out class)。是我们类的第五大成员【属性、方法、构造器、代码块、内部类】,内部类最大的特点就是直接访问私有属性,并且可以体现类于类之间包含关系。【注意:内部类是学习的难点,同时也是重点】


基本语法

class Outer{//外部类
	class Inner{//内部类

	}
}
class Other{//外部其他类
}

快速入门案例

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类的内部

    }
}


内部类的分类

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

1)局部内部类(有类名)
2)匿名内部类(没有类名,重点)

定义在外部类的成员位置上:

1)成员内部类(没有static修饰)
2)静态内部类(使用static修饰)


局部内部类的使用

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

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

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

package com.zzpedu.innerclass;

/**
 * 演示局部内部类的使用
 */
public class LocalInnerClass {
    public static void main(String[] args) {
        //演示一下
        Outer02 outer02 = new Outer02();
        outer02.m1();
    }
}
class Outer02{//外部类
    private int n1 = 200;//属性
    private void m2(){//私有方法
        System.out.println("Outer02外部类的m2()方法...");
    }
    public void m1(){//方法
        //1.局部内部类是定义在外部的局部位置,通常在方法中
        //3.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。
        // 但是可以使用final 修饰,因为局部变量也可以使用final
        //4.作用域:仅仅在定义它的方法或代码块中。
        final class Inner02{//局部内部类(本质仍然是一个类)
            //2.可以直接访问外部类的所有成员,包含私有的
            public void f1(){
                //5.局部内部类可以直接访问外部类的成员,比如下面 外部类的n1 和m2()
                System.out.println("n1=" + n1);
                m2();
            }
        }
        //6.外部类在方法中,可以创建Inner02对象实例,然后调用方法
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
    {//代码块
        class Inner03{
        }
    }
}
=============控制台输出=========================
n1=200
Outer02外部类的m2()方法...
  1. 外部类其他类 – 不能访问 —> 局部内部类(因为 局部内部类地位是一个局部变量)
  2. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

System.out.println(“外部类的n2=” + 外部类.this.n2);

/**
 * 演示局部内部类的使用
 */
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 = 200;//属性
    private void m2(){//私有方法
        System.out.println("Outer02外部类的m2()方法...");
    }
    public void m1(){//方法
        final class Inner02{//局部内部类(本质仍然是一个类)
            private int n1 = 800;
            public void f1(){
                //7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
                // 则可以使用(外部类名.this.成员)去访问
                // Outer02.this.n1 本质就是外部类的对象,即哪个对象调用了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();
    }
}
=============控制台输出=========================
局部内部类n1=800 外部类n1=200
Outer02.this hashcode=com.zzpedu.innerclass.Outer02@1b6d3586
Outer02外部类的m2()方法...
outer02的hashcode=com.zzpedu.innerclass.Outer02@1b6d3586

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

(1)本质是类
(2)内部类
(3)该类没有名字
(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.传统方法,是写一个类,实现该接口,并实创对象
        //3.需求是 Tiger 类只是使用一次,后面再不使用
        //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();
        tiger.cry();
        tiger.cry();
//        IA tiger = new Tiger();
//        tiger.cry();
    }
}
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("小狗汪汪...");
//    }
//}
=================控制台输出==========================
tiger的运行类型=class com.zzpedu.innerclass.Outer04$1
老虎叫唤...
老虎叫唤...
老虎叫唤...
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04{//外部类
    private int n1 = 10;//属性
    public void method(){//方法
        IA tiger = new IA(){
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger的运行类型=" + tiger.getClass());
        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("小狗啃骨头...");
            }
        };
        System.out.println("animal对象的运行类型=" + animal.getClass());//Outer04$3
        animal.eat();
    }
}
interface IA{//接口
    public void cry();
}

class Father{//类
    public Father(String name){//构造器
        System.out.println("Father的构造器name=" + name);
    }
    public void test(){//方法
    }
}
abstract class Animal{
    abstract void eat();
}
=================控制台输出==========================
tiger的运行类型=class com.zzpedu.innerclass.Outer04$1
老虎叫唤...
Father的构造器name=jack
father对象的运行类型=class com.zzpedu.innerclass.Outer04$2
匿名内部类重写test()方法
animal对象的运行类型=class com.zzpedu.innerclass.Outer04$3
小狗啃骨头...

2、匿名内部类的语法比较奇特
请大家注意,因为匿名内部类即是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法

new A(){
	@Verride
	public void cry(){
		System.out.println("hello~");
	}
}.cry();
A a  = new A(){
	@Verride
	public void cry(){
		System.out.println("hello~");
	}
};
a.cry();
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(){
            @Override
            public void hi() {
                System.out.println("匿名内部类重写了 hi方法");
            }
        };
        p.hi();//动态绑定,运行类型是 outer05$1

        //也可以直接调用,匿名内部类本身也是返回对象
        // class 匿名内部类 extends Person{ }
        new Person(){
            @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);
    }
}
//抽象类/接口...
==========控制台输出==========
匿名内部类重写了 hi方法
Person ok()方法 参数=jack

3、可以直接访问外部类的使用成员,包含私有的

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

5、作用域:仅仅在定义它的方法或代码块中

6、匿名内部类 – 访问 —> 外部类成员 【访问方式:直接访问】

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

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

public class AnonymousInnerClassDetail {
    public static void main(String[] args) {
        //创建
        Outer05 outer05 = new Outer05();
        outer05.f1();
        //外部类其他类 – 不能访问 —> 匿名内部类(因为 匿名内部类地位是一个局部变量)
        System.out.println("main方法 outer05 hashcode=" + outer05);
    }
}
class Outer05{
    private int n1 = 99;
    public void f1(){
        //创建一个基于类的匿名内部类
        //不能添加访问修饰符,因为它的地位就是一个局部变量
        //作用域:仅仅在定义它的方法或代码块中 outer05$1
        Person p = new Person(){
            private int n1 = 88;
            @Override
            public void hi() {
                //可以直接访问外部类的使用成员,包含私有的
                //如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
                //  默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi方法 n1=" + n1 +
                        " 外部类的n1="  + Outer05.this.n1);
                //Outer05.this 就是调用 f1的 对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
           }
        };
        p.hi();//动态绑定,运行类型是 outer05$1

    }
}
class Person{//类
    public void hi(){
        System.out.println("Person hi()方法");
    }
    public void ok(String str){
        System.out.println("Person ok()方法 参数=" + str);
    }
}
==========控制台输出==========
匿名内部类重写了 hi方法 n1=88 外部类的n1=99
Outer05.this hashcode=com.zzpedu.innerclass.Outer05@1b6d3586
main方法 outer05 hashcode=com.zzpedu.innerclass.Outer05@1b6d3586

匿名内部类的最佳实际

1、当做实参直接传递,简洁高效

public class InnerClassExercise01 {
    public static void main(String[] args) {
        //当做实参直接传递,简洁高效
        f1(new IL() {
            @Override
            public 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{
    @Override
    public void show() {
        System.out.println("这时一副名画...");
    }
}
==========控制台输出==========
这时一副名画~~...
这时一副名画...

2、(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() {
            @Override
            public void ring() {
                System.out.println("懒猪起床了");
            }
        });

        cellPhone.alarmClock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小伙伴上课了");
            }
        });
    }
}
interface Bell{//接口
    void ring();//方法
}
class CellPhone{//类
    public void alarmClock(Bell bell){//形参是Bell接口类型
        System.out.println("bell.getClass()=" + bell.getClass());
        bell.ring();//动态绑定
    }
}
==========控制台输出==========
bell.getClass()=class com.zzpedu.innerclass.InnerClassExercise02$1
懒猪起床了
bell.getClass()=class com.zzpedu.innerclass.InnerClassExercise02$2
小伙伴上课了

匿名内部类:(1)继承 (2)多态 (3)动态绑定 (4)内部类



成员内部类的使用

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

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

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

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
    }
}
class Outer08{//外部类
    private int n1 = 10;
    public String name = "张三";

    //1.注意:成员内部类,是定义在外部类的成员位置,并且没有static修饰。
    //2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    public class Inner08{//成员内部类
        public void say(){
            //可以直接访问外部类的所有成员,包含私有的
            System.out.println("n1=" + n1 + " name=" + name);
        }
    }
    //方法
    public void t1(){
        //使用成员内部类
        Inner08 inner08 = new Inner08();
        inner08.say();
    }
}
==========控制台输出==========
n1=10 name=张三

3、作用域:和外部类其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。

4、成员内部类 --> 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】

5、外部类 --> 访问—> 成员内部类【访问方式:创建对象,在访问】

6、外部其他类 --> 访问 --> 成员内部类

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

public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();

        //外部其他类,使用成员内部类的三种方式
        //解读:
        // 第1种方式
        // outer08.new Inner08();相当于把 new Inner08()当做是outer08成员
        Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();
        // 第2种方式
        // 在外部类中,编写一个方法,可以返回 Inner08对象
        Outer08.Inner08 inner08Instance = outer08.getInner08();
        inner08Instance.say();
        // 第3种方式
        Outer08.Inner08 inner081 = new Outer08().new Inner08();
    }
}
class Outer08{//外部类
    private int n1 = 10;
    public String name = "张三";

    private void hi(){
        System.out.println("hi()方法");
    }

    public class Inner08{//成员内部类
        private double sal = 99.9;
        private int n1 = 66;
        public void say(){
            //可以直接访问外部类的所有成员,包含私有的
            //如果成员内部类的成员和外部类的成员重名,遵守就近原则
            //,可以通过 外部类名.this.属性 来访问外部类的成员
            System.out.println("n1=" + n1 + " name=" + name +
                    " 外部类的n1=" + Outer08.this.n1);
            hi();
        }
    }
    //该方法,返回一个Inner08实例
    public Inner08 getInner08(){
        return new Inner08();
    }

    //方法
    public void t1(){
        //使用成员内部类
        //创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}
==========控制台输出==========
n1=66 name=张三 外部类的n1=10
hi()方法
99.9
n1=66 name=张三 外部类的n1=10
hi()方法
n1=66 name=张三 外部类的n1=10
hi()方法



静态内部类的使用

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

1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

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

3、作用域:同其他的成员,为整个类体

4、静态内部类 – 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】

5、外部类 – 访问 —> 静态内部类【访问方式:创建对象,再访问】

6、外部其他类 – 访问 —> 静态内部类

new 外部类.静态内部类
编写一个方法,可以返回静态内部类

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

public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();
        //外部其他类 使用 静态内部类
        //方式一
        //因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
        //方式二
        //编写一个方法,可以返回静态内部类
        Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("---------");
        inner101.say();
        System.out.println("---------");
        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        inner10_.say();
    }
}
class Outer10{//外部类
    private int n1 = 10;
    public static String name = "张三";
    private static void cry(){ }
    //Inner10 就是静态内部类
    //1. 放在外部类成员位置
    //2. 使用static 修饰
    //3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
    //4. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
    //5. 作用域:同其他的成员,为整个类体
    public static class Inner10{
        private static String name = "zzp";
        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();
    }
}
==========控制台输出==========
zzp 外部类name=张三
zzp 外部类name=张三
---------
zzp 外部类name=张三
---------
zzp 外部类name=张三

小结:
(1)内部类有四种,局部内部类,匿名内部类,成员内部类,静态内部类
(2)重点还是掌握 匿名内部类的使用

new 类/接口(参数列表){ //…};

(3)成员内部类,静态内部类 是放在外部类的成员位置,本质就是一个成员



测试题

1、写出输出结果

public class Test{//外部类
	public Test(){//构造器
		Inner s1 = new Inner();
		s1.a = 10;
		Inner s2 = new Inner();
		System.out.println(s2.a);
	}
	class Inner{//成员内部类
		public int a = 5;
	}
	public static void main(String[] args) {
		Test t = new Test();//构造器输出 5
		Inner r = t.new Inner();//创建成员内部类对象
		System.out.println(r.a);//5
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值