类变量和类方法(静态变量和静态方法)
类变量(静态变量)
引入
- 提出问题:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?编写程序解决
传统的方法来解决
思路:
- 在main方法中定义一个变量count
- 当一个小孩加入游戏后,count++,最后count就记录有多少个小孩玩游戏
package com.zanedu.static_;
public class ChildGame {
public static void main(String[] args) {
//定义变量count,统计有多少小孩加入了游戏
Child child1 = new Child("白骨精");
child1.join();
count++;
Child child2 = new Child("狐狸精");
child2.join();
count++;
Child child3 = new Child("老鼠精");
child3.join();
count++;
}
}
class Child {
private String name;
//定义一个变量count,是一个类变量(静态变量) static 静态
//Java8存储在静态区(方法区),后面的版本存储在堆中
//默认只会用一次
//该变量的最大的特点就是会被Child类的所有的实例对象共享
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了游戏");
}
}
问题分析:
- count是一个独立于对象
- 以后我们访问count很麻烦,没有使用到OOP
- 因此,我们引出类变量/静态变量
类变量快速入门
- 思考:如果,设计一个int count 表示总人数,我们在创建一个小孩时,就把count加1,并且count是所有对象共享的ok了,我们使用类变量来解决
package com.zanedu.static_;
public class ChildGame {
public static void main(String[] args) {
//的定义变量count,统计有多少小孩加入了游戏
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);
System.out.println("child2.count=" + child2.count);
System.out.println("child3.count=" + child3.count);
}
}
class Child {
private String name;
//定义一个变量count,是一个类变量(静态变量) static 静态
//Java8存储在静态区(方法区),后面的版本存储在堆中
//默认只会用一次
//该变量的最大的特点就是会被Child类的所有的实例对象共享
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了游戏");
}
}
类变量内存布局
- 问题:静态变量放在哪里?
有些书说在方法区,和JDK版本有关,但是记住一点:
static变量是对象共享,不管static变量在哪里
共识:
- static 变量是同一个类所有对象共享
- static 类变量,在类加载的时候就生成了
类变量的基本介绍
什么是类变量
- 类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量
如何定义类变量
- 访问修饰符 static 数据类型 变量名;(推荐)
- static 访问修饰符 数据类型 变量名;
如何访问类变量
- 类名.类对象名(推荐)
- 对象名.类变量名
- 注意:静态变量的访问修饰符的访问权限和范围和普通属性是一样的
类变量使用注意事项和细节讨论
- 什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name, static fee)
- 类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
- 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 类变量可以通过类名.类变量名或者对象名.类变量名来访问,但Java设计者推荐我们使用类名.类变量名方式访问(前提:满足访问修饰符的访问权限和范围)
- 实例变量不能通过类名.类变量名的方式访问
package com.zanedu.static_;
public class StaticDetail {
public static void main(String[] args) {
B b = new B();
System.out.println(B.n1);//error
System.out.println(B.n2);
}
}
class B {
public int n1 = 100;
public static int n2 = 200;
}
- 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
package com.zanedu.static_;
public class StaticDetail {
public static void main(String[] args) {
//静态变量是类加载的时候,就创建了,所以我们没有创建对象实例
//也可以通过类名.类变量名来访问
System.out.println(C.address);
}
}
class C {
public static String address = "北京";
}
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
类方法(静态方法)
类方法的基本介绍
- 类方法也叫静态方法
类方法的语法形式**:**
- 访问修饰符 static 数据返回类型 方法名() { } 【推荐】
- static 访问修饰符 数据返回类型 方法名() { }
类方法的调用
-
使用方式:
-
- 1. 类名.类方法名
- 2. 对象名.类方法名
- 前提:满足访问修饰符的访问权限和范围
类方法经典的使用场景
- 当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
比如:工具类中的方法utils、Math类、Arrays类、Collections集合类
- 小结:
在程序员实际开发中,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组、冒泡排序、完成某个计算任务等
类方法使用注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
类方法中无this的参数
普通方法中隐含着this的参数
- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
package com.zanedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();
//非静态方法,不能通过类名调用
D.say();//error
}
}
class D {
public void say() { //非静态方法
}
public static void hi() { //静态方法,类方法
//类方法中不允许使用和对象有关的关键字
//比如this和super,普通方法(成员方法)可以
// this.n1;
// super.n1;
// return;
// System.out.println("1");
}
}
- 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
- 类方法(静态方法)中只能访问静态变量或静态方法
package com.zanedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();
//非静态方法,不能通过类名调用
// D.say();//error
new D().say();//ok
}
}
class D {
public int n1 = 100;
private static int n2 = 200;
public void say() { //非静态方法
}
public static void hi() { //静态方法,类方法
//类方法中不允许使用和对象有关的关键字
//比如this和super,普通方法(成员方法)可以
// this.n1;
// super.n1;
// return;
// System.out.println("1");
}
//类方法(静态方法)中 只能访问 静态变量或静态方法
//口诀:静态方法只能访问静态方法或变量
public static void hello() {
System.out.println(n2);
System.out.println(D.n2);
// System.out.println(this.n2);//error
hi();
// say();//error
}
}
- 普通成员方法,既可以访问非静态成员,也可以访问静态成员
package com.zanedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();
//非静态方法,不能通过类名调用
// D.say();//error
new D().say();//ok
}
}
class D {
public int n1 = 100;
private static int n2 = 200;
public void say() { //非静态方法
}
public static void hi() { //静态方法,类方法
//类方法中不允许使用和对象有关的关键字
//比如this和super,普通方法(成员方法)可以
// this.n1;
// super.n1;
// return;
// System.out.println("1");
}
//普通成员方法,既可以访问非静态成员,也可以访问静态成员
public void ok() {
//非静态成员
System.out.println(n1);
System.out.println(n2);
say();
hi();
hello();
}
}
- 小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(但必须遵守访问权限)
补充:static修饰的方法不能被重写
理解main方法语法
深入理解main方法
**解释main方法的形式:**public static void main(String[] args) { }
- main方法是Java虚拟机调用的
- Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接受String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
- Java 执行的程序 参数1 参数2 参数3
-
args就是在执行程序的时候传进去的
-
将三个字符串打包成一个数组,然后传入进去
特别提示:
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
package com.zanedu.main_;
public class Main01 {
//静态的变量/属性
private static String name = "zanedu";
//静态方法
public static void hi() {
System.out.println("Main01 的hi方法");
}
public static void main(String[] args) {
//可以直接使用name
//静态方法可以访问本类的静态成员
System.out.println(name);
hi();
}
}
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
package com.zanedu.main_;
public class Main01 {
//静态的变量/属性
private static String name = "zanedu";
//非静态的变量/属性
private int n1 = 10000;
//静态方法
public static void hi() {
System.out.println("Main01 的hi方法");
}
//非静态方法
public void ok() {
System.out.println("Main01 的ok方法");
}
public static void main(String[] args) {
//可以直接使用name
//静态方法可以访问本类的静态成员
System.out.println(name);
hi();
//静态方法main,不能访问本类的非静态方法
// System.out.println(n1);
// ok();
//静态方法main 要访问本类的非静态成员,需要先创建对象,再调用即可
Main01 main01 = new Main01();
System.out.println(main01.n1);
main01.ok();
}
}
- 不能直接访问该类中的非静态成员
- 必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
- 补充:在idea里传递参数
代码块
基本介绍
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来
- 注意:但和方法不同,它没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或****创建对象时隐式调用
基本语法
[修饰符]{
代码
};
- 说明注意:
- 修饰符****可写可不写,要写的话,也只能写static
- **代码块分为两类,**使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ;号(分号)可以写上,也可以省略
代码块的好处和案例演示
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到代码化块中,从而提高代码的重用性
package com.zanedu.codeblock_;
public class codeblock {
public static void main(String[] args) {
Movie movie = new Movie("你好,李焕英");
Movie movie1 = new Movie("唐探3", 100, "陈思成");
}
}
class Movie {
private String name;
private double price;
private String director;
//构造器 - 重载
//解读
//(1)下面的三个构造器都有相同的语句
//(2)这样代码看起来比较冗余
//(3)我们可以把相同的语句放到一个代码块中,即可
//(4)这样当我们不管调用哪个构造器创建对象,都会先调用代码块的内容
//(5)代码块调用的顺序优先于构造器
static {
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) {
System.out.println("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;
}
}
代码块使用注意事项和细节讨论
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,就会执行一次
- 只调用了一次
-
类什么时候被加载(重点)
-
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性、静态方法)
package com.zanedu.codeblock_;
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);
}
}
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被执行....");
}
}
class AA extends BB {
//静态代码块
static {
System.out.println("AA 的静态代码块1被执行....");
}
}
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性、静态方法)
- **普通的代码块,在创建对象实例时,会被隐式的调用,被创建一次,就会被调用一次。**如果只是使用类的静态成员,那么普通代码块并不会执行
package com.zanedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//static代码块,是在类加载时,执行的,并且只会执行一次
// DD dd = new DD();
// DD dd1 = new DD();
//普通的代码块,在创建对象实例时,会被隐式的调用
//被创建一次,就会调用一次
//如果只是使用类的静态成员时,普通代码块并不会执行
System.out.println(DD.n1);//888,静态代码块一定会执行
}
}
class DD {
public static int n1 = 888;//静态属性
//静态代码块
static {
System.out.println("DD 的静态代码块1被执行....");
}
//普通代码块,在new对象时,被调用,而且是每创建一个对象,就调用一次
//类加载跟普通代码块没有任何关系
//普通代码块是构造器的补充
{
System.out.println("DD 的普通代码块....");
}
}
- static代码块,是在类加载时执行的,并且只会执行一次
- 普通代码块,创建对象一次就会执行一次
小结:
-
static代码块是类加载时,执行,并且只会执行一次
-
普通代码块是在创建对象时调用的,创建一次,就会调用一次
-
类加载的3种情况,重点
-
创建一个对象时,在一个类调用顺序是:(重点、难点)
-
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
- 调用构造方法(即构造器)【构造器的优先级是最低的】
package com.zanedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
}
}
class A {
//静态属性的初始化
private static int n1 = getN1();
static { //静态代码块
System.out.println("A 静态代码块01");
}
public static int getN1() {
System.out.println("getN1被调用");
return 100;
}
}
-
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
package com.zanedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
}
}
class A {
private int n2 = getN2();//普通属性的初始化
{ //普通代码块
System.out.println("A 的普通代码块01");
}
//静态属性的初始化
private static int n1 = getN1();
static { //静态代码块
System.out.println("A 静态代码块01");
}
public static int getN1() {
System.out.println("getN1被调用");
return 100;
}
public int getN2() { //普通方法
System.out.println("getN2被调用");
return 200;
}
}
-
- 调用构造方法(即构造器)
package com.zanedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
}
}
class A {
//无参构造器
public A() {
System.out.println("A() 无参构造器被调用");
}
private int n2 = getN2();//普通属性的初始化
{ //普通代码块
System.out.println("A 的普通代码块01");
}
//静态属性的初始化
private static int n1 = getN1();
static { //静态代码块
System.out.println("A 静态代码块01");
}
public static int getN1() {
System.out.println("getN1被调用");
return 100;
}
public int getN2() { //普通方法
System.out.println("getN2被调用");
return 200;
}
}
- 注意:不管你构造器定义在前在后,都是最后调用
- **构造器的最前面其实****隐含了super()和调用普通代码块,**静态相关的代码块,属性初始化,在类加载时,就执行完毕了,因此是优先于构造器和普通代码块执行的
class A {
public A() { //构造器
//这里有隐藏的执行要求 - 不显示
//(1)super();
//(2)调用普通代码块
System.out.println(“OK”);
}
}
package com.zanedu.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new 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有普通代码块
-
创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
-
父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
-
子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
-
父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
-
父类的构造方法(即构造器)
-
子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
-
子类的构造方法(即构造器)
package com.zanedu.codeblock_;
public class CodeBlockDetail04 {
public static void main(String[] args) {
//说明
//(1)进行类的加载
//1.1 先加载父类AAAA
//1.2 再加载子类BBBB
//(2)创建对象
//2.1 从子类的构造器开始
new BBBB();//对象
/**
* getVal01方法被调用
* AAAA的一个静态代码块....
* getVal03方法被调用
* BBBB的一个静态代码块....
* AAAA的一个普通代码块....
* getVal02方法被调用
* AAAA 的构造器
* BBBB的一个普通代码块....
* getVal04方法被调用
* BBBB 的构造器
*/
}
}
class AAAA {
private static int n1 = getVal01();
static {
System.out.println("AAAA的一个静态代码块....");
}
{
System.out.println("AAAA的一个普通代码块....");
}
private int n3 = getVal02();
public static int getVal01() {
System.out.println("getVal01方法被调用");
return 10;
}
public int getVal02() {
System.out.println("getVal02方法被调用");
return 10;
}
public AAAA() {
//隐藏了
//super()
//普通代码块....
System.out.println("AAAA 的构造器");
}
}
class BBBB extends AAAA{
private static int n2 = getVal03();
static {
System.out.println("BBBB的一个静态代码块....");
}
{
System.out.println("BBBB的一个普通代码块....");
}
private int n3 = getVal04();
public static int getVal03() {
System.out.println("getVal03方法被调用");
return 10;
}
public int getVal04() {
System.out.println("getVal04方法被调用");
return 10;
}
public BBBB() {
//隐藏了
//super();
//普通代码块
System.out.println("BBBB 的构造器");
}
}
- 静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
package com.zanedu.codeblock_;
public class CodeBlockDetail04 {
public static void main(String[] args) {
new C02();
}
}
class C02 {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}
static {
//静态代码块,只能调用静态成员
// System.out.println(n1);//error
System.out.println(n2);//ok
// m1();//error
m2();
}
{
//普通代码块,可以使用任意成员
System.out.println(n1);//ok
System.out.println(n2);//ok
m1();//ok
m2();//ok
}
}
- 静态代码块不能调用非静态成员
单例设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考
什么是单例模式
单例(单个的实例)
- 所谓类的单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式(饿汉式、懒汉式)
单例模式应用实例
步骤:
- 构造器私有化 ==> 防止直接new
- 类的内部创建对象
- 向外暴露一个静态的公共方法 getInstance
- 饿汉式
package com.zanedu.single_;
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend girlFriend = new GirlFriend("小红");
// GirlFriend girlFriend2 = new GirlFriend("小白");
//通过方法可以获取对象
GirlFriend girlFriend = GirlFriend.getInstance();
System.out.println(girlFriend);
GirlFriend girlFriend1 = GirlFriend.getInstance();
System.out.println(girlFriend1);
System.out.println(girlFriend == girlFriend1);//true
//System.out.println(GirlFriend.n1);
}
}
//有一个类,GirlFriend
//只能有一个女朋友
class GirlFriend {
private String name;
public static int n1 = 100;
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为static
//对象,通常是重量级对象,饿汉式可能造成了创建了对象,但是没有使用
private static GirlFriend gf = new GirlFriend("小红");
//如何保证我们只能创建一个GirlFriend对象
//步骤:【单例模式 - 饿汉式】 - 还没用到这个对象,但是这个对象已经创建好了
//1. 将构造器私有化
//2. 在类的内部直接创建(该对象是static)
//3. 提供一个公共的静态方法,返回gf对象
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
- 懒汉式
package com.zanedu.single_;
/**
* 演示懒汉式的单例模式
*/
public class SingleTon02 {
public static void main(String[] args) {
// System.out.println(Cat.n1);
Cat cat = Cat.getInstance();
System.out.println(cat);
//再次调用getInstance()
Cat cat1 = Cat.getInstance();
System.out.println(cat1);
System.out.println(cat == cat1);//true
}
}
//希望在程序运行过程中,只能创建一个Cat对象
//使用单例模式
class Cat {
private String name;
public static int n1 = 999;
private static Cat cat; //默认是null
//步骤
//1. 仍然将构造器私有化
//2. 定义一个静态属性对象
//3. 提供一个公共的static方法,可以返回一个Cat对象
//4. 懒汉式,只有当用户使用getInstance方法时,才返回Cat对象,而且后面再次调用后,会返回上次创建的Cat对象
// 从而保证了单例
// Runtime;
private Cat(String name) {
System.out.println("构造器调用");
this.name = name;
}
public static Cat getInstance() {
if (cat == null) { //如果还没有创建cat对象
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
饿汉式 VS 懒汉式
- 二者最主要的区别在于创建对象的时机不同,饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
- 在外面JavaSE标准类中,java.lang.Runtime就是经典的单例模式
final关键字
基本介绍
final 可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到final
- 当不希望类被继承时,可以用final修饰
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰
- 当不希望类的某个属性的值被修改,就可以用final修饰
- 当不希望某个局部变量被修改,可以使用final修饰
package com.zanedu.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方法");
}
}
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);
}
}
- 要求A类不能被其他类继承
- 父类方法不能被子类重写
- 不希望类的某个属性的值被修改
- 不希望局部变量被修改
final使用注意事项和细节讨论
- final修饰的属性又叫常量,一般用XX_XX_XX来命名
-
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一(选择一个位置赋初值即可)
-
- 定义时:如public final double TAX_RATE = 0.08;
- 在构造器中
- 在代码块中
package com.zanedu.final_;
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC();
new EE().cal();
}
}
class AA {
/*
1. 定义时,如public final double TAX_RATE = 0.08;
2. 在构造器中
3. 在代码块中
*/
public final double TAX_RATE = 0.08;//定义时赋值
public final double TAX_RATE2;
public final double TAX_RATE3;
public AA() { //构造器中赋值
TAX_RATE2 = 1.1;
}
{//代码块中赋值
TAX_RATE3 = 8.8;
}
}
- 常量必须初始化
- 三种地方的定义
-
如果final修饰的属性是静态的,则初始化的位置只能是
-
- 定义时
- 在静态代码块,不能再构造器中赋值
package com.zanedu.final_;
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC();
new EE().cal();
}
}
class BB {
/*
如果final修饰的属性是静态的,则初始化的位置只能是
1. 定义时
2. 在静态代码块
不能在构造器中
*/
public static final double TAX_RATE = 99.9;
public static final double TAX_RATE2;
// public static final double TAX_RATE3;
// public BB() {
// TAX_RATE3 = 8.8;
// }
static {
TAX_RATE2 = 3.3;
}
}
- 在类加载的时候就要初始化了,而在构造器中只有创建对象才能调用,因此不能在构造器中赋值
- final类不能继承,但是可以实例化对象
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
package com.zanedu.final_;
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC();
new EE().cal();
}
}
//final类不能继承,但是可以实例化对象
final class CC {}
//如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但是可以被继承
//即仍然遵守继承的机制
class DD {
public final void cal() {
System.out.println("cal() 方法");
}
}
class EE extends DD {
}
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法了
final class AAA {
//一般来说,如果一个类已经时final类了,就没有必要再将方法修饰成final方法
// public final void cry() {}
}
- final不能修饰构造方法(即构造器)
- final 和 static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
package com.zanedu.final_;
public class FinalDetail02 {
public static void main(String[] args) {
System.out.println(BBB.num);
//包装类,String是final类,不能被继承
// Double
}
}
//final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
class BBB {
public final static int num = 10000;
static {
System.out.println("静态代码块被执行");
}
}
- 包装类(Integer、Double、Float、Boolean等都是final),String也是final类
抽象类
引入
- 看一个小问题,看一个程序
package com.zanedu.abstract_;
public class Abstract01 {
public static void main(String[] args) {
}
}
abstract class Aniaml {
private String name;
public Aniaml(String name) {
this.name = name;
}
//思考:这里eat实现了,但是没有什么意义
//即父类方法不确定性的问题
//====> 考虑将该方法设计为抽象(abstract)方法
//所谓抽象方法就是没有实现的方法
//所谓没有实现就是指,没有方法体
//当一个类中存在抽象方法时,需要将该类声明为abstract类
//一般来说,抽象类会被继承,由其子类来实现抽象方法
// public void eat() {
// System.out.println("这是一个动物,但是不知道吃什么..");
// }
public abstract void eat();
}
- 小结:当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
- 正确形式
抽象类快速入门
- 当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个就是抽象方法,用abstract来修饰该类就是抽象类
抽象类的基本介绍
- 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名 {
}
- 用abstract关键字来修饰一个方法时,这个方法就是抽象方法
**访问修饰符 abstract 返回类型 方法名(参数列表); //**没有方法体
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类{}
抽象类使用的注意事项和细节讨论
- 抽象类不能被实例化
package com.zanedu.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
//抽象类不能被实例化
new A();
}
}
abstract class A {
}
- 抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法
//抽象类不一定要包含abstract方法,也就是说,抽象类可以没有abstract方法
//还可以有实现的方法
abstract class A {
public void hi() {
System.out.println("hi");
}
}
- 没有报错
- 一旦类包含了abstract方法,则这个类必须声明为abstract
//一旦类包含了abstract方法,则这个类必须声明为abstract
class B { //error
public abstract void hi() {
}
}
- abstract只能修饰类和方法,不能修饰属性和其他的
//abstract 只能修饰类和方法,不能修饰属性和其他的东西
class C {
public abstract int n1 = 1;//error
}
- 抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
//抽象类的本质还是类,所以可以有类的各种成员
abstract class D {
public int n1 = 10;
public static String name = "zan";
public void hi() {
System.out.println("hi");
}
public abstract void hello();
public static void ok() {
System.out.println("ok");
}
}
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
//如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class E {
public abstract void hi();
}
abstract class F extends E {
//error
}
class G extends E {
public void hi() { //相当于G子类实现了父类E的抽象方法,所谓实现方法,就是要有方法体
}
}
- 抽象方法不能使用private、final、static来修饰,因为这些关键字都是和重写相违背的
//抽象方法不能使用private、final 和 static来修饰,因为这些关键字都是和重写相违背的
abstract class H {
public abstract void hi(); //ok - 抽象方法
}
- 不能用private
- 不能用final
- 不能用static
练习题
- Employee类
package com.zanedu.abstract_;
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;
}
}
- Manager类
package com.zanedu.abstract_;
public class Manage extends Employee {
private double bonus;
public Manage(String name, int id, double salary) {
super(name, id, salary);
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("经理 " + getName() + "工作中");
}
}
- CommonEmployee类
package com.zanedu.abstract_;
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() + "工作中");
}
}
- AbstractExercise类
package com.zanedu.abstract_;
public class AbstractExercise {
public static void main(String[] args) {
Manage manage = new Manage("jack", 999, 200000);
manage.setBonus(80000);
manage.work();
CommonEmployee commonEmployee = new CommonEmployee("tom", 888, 200000);
commonEmployee.work();
}
}
抽象类最佳实践-模板设计模式
基本介绍
- 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
模板设计模式能解决的问题
- 当功能内部一部分实现是确定的,一部分实现是不确定的,这时就可以把不确定的部分暴露出去,让子类去实现
- 编写一个抽象父类,父类提供多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式
实践
需求:
- 有多个类,完成不同的任务job
- 要求统计得到各自完成任务的实践
- Template类
package com.zanedu.abstract_;
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类
package com.zanedu.abstract_;
public class AA extends Template{
// public void calculateTime() {
// //得到开始的时间
// long start = System.currentTimeMillis();
// job();
// //得到结束的时间
// long end = System.currentTimeMillis();
// System.out.println("AA 执行时间=" + (end - start));
// }
//计算任务
//1+...+9000000
public void job() { //这里也去,重写Template的job方法
long num = 0;
for (long i = 1; i <= 9000000; i++) {
num += i;
}
}
}
- BB类
package com.zanedu.abstract_;
public class BB extends Template{
// public void calculateTime() {
// //得到开始的时间
// long start = System.currentTimeMillis();
// job();
// //得到结束的时间
// long end = System.currentTimeMillis();
// System.out.println("BB 执行时间=" + (end - start));
// }
public void job() { //实现Template的抽象方法job
long num = 0;
for (long i = 1; i <= 9000000; i++) {
num *= i;
}
}
}
- TestTemplate类
package com.zanedu.abstract_;
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime(); //这里需要良好的OOP基础,多态
BB bb = new BB();
bb.calculateTime();
}
}
接口
引入+快速入门
- Interface01类
package com.zanedu.interface_;
public class Interface01 {
public static void main(String[] args) {
//创建手机、相机对象
Camera camera = new Camera();
Phone phone = new Phone();
//创建计算机
Computer computer = new Computer();
computer.work(phone); //把手机接入到计算机
computer.work(camera); //把相机接入到计算机
}
}
- UsbInterface接口
package com.zanedu.interface_;
public interface UsbInterface { //接口
//规定接口的相关方法 - 人为规定的,即规范
public void start();
public void stop();
}
- Computer类
package com.zanedu.interface_;
public class Computer {
//编写一个方法,计算机工作
//解读:
/*
1. UsbInterface usbInterface 形参是接口类型UsbInterface
2. 看到 接受 实现了 UsbInterface接口的类的对象实例
*/
public void work(UsbInterface usbInterface) {
//通过接口来调用方法
usbInterface.start();
usbInterface.stop();
}
}
- Phone类
package com.zanedu.interface_;
//Phone类 实现 UsbInterface
/*
1. 即Phone类需要实现 UsbInterface接口 规定/声明的方法
*/
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始工作.....");
}
@Override
public void stop() {
System.out.println("手机停止工作.....");
}
}
- Camera类
package com.zanedu.interface_;
public class Camera implements UsbInterface{ //实现接口,就是把接口的方法实现
@Override
public void start() {
System.out.println("相机开始工作....");
}
@Override
public void stop() {
System.out.println("相机停止工作....");
}
}
基本介绍
- 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来
语法:
interface 接口名 {
//属性
//抽象方法、默认实现方法、静态方法
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}
- 小结:
接口是更加抽象的抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体【JDK7.0】。接口体现了程序设计的多态和高内聚低耦合的设计思想
-
特别说明:
-
- JDK7.0前,接口里的所有方法都是抽象方法
- JDK8.0后,接口可以有静态方法、默认方法
-
AInterface接口
package com.zanedu.interface_;
public interface AInterface {
//写属性
public int n1 = 10;
//写方法
//在接口中,抽象方法可以省略abstract关键字
public abstract void hi();
//在jdk8后,可以有默认实现方法,但是需要使用default关键字修饰
default public void ok() {
System.out.println("ok()....");
}
//在jdk8后,可以有静态方法
public static void cry() {
System.out.println("cry()....");
}
}
- 默认实现方法,需要用default关键字修饰
- 可以有静态方法
深入讨论
对初学者讲,理解接口的概念不算太难,难的是不知道什么时候使用接口,例句几个应用场景
- 现在要制造战斗机、武装直升机,而专家只需把飞机需要的功能/规格定下来即可,然后让别的人具体实现即可
- 现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现
需求:三个程序员,编写三个类,分别完成对Mysql、Oracle、DB2数据库的连接connect、close
- DBInterface接口
package com.zanedu.interface_;
public interface DBInterface { //项目经理
public void connect();//连接方法
public void close();//关闭连接方法
}
- OracleDB类 - 实现了DBInterface接口
package com.zanedu.interface_;
//B程序员连接Oracle
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接oracle");
}
@Override
public void close() {
System.out.println("关闭oracle");
}
}
- MysqlDB类 - 实现了DBInterface接口
package com.zanedu.interface_;
public class MysqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接mysql");
}
@Override
public void close() {
System.out.println("关闭mysql");
}
}
- Interface03类 - 主类
package com.zanedu.interface_;
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 dbInterface) {
dbInterface.connect();
dbInterface.close();
}
}
注意事项和细节
- 接口不能被实例化(即不能new对象)
package com.zanedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
new IA();//error
}
}
//1. 接口不能被实例化
interface IA {
}
- 接口中所有的方法都是public方法,接口中抽象对象,可以不用abstract修饰
void aaa(); ==> 实际上是 abstract void aaa();
//2. 接口中所有的方法是public方法,可以不写,接口中抽象方法,可以不用abstract修饰
interface IA {
public abstract void say();
void say1();
protected void say2();//error
void hi();
}
- 接口中所有的方法都是public方法
- 访问修饰符是默认如何判断 - 在别的实现类里面修改看一下,是否报错
- 一个普通类实现接口,就必须将该接口的所有方法都实现
//3. 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用alt + enter来解决
interface IA {
// public abstract void say();
// void say1();
void say(); //修饰符 public protected 默认 private
void hi();
}
class Cat implements IA { // alt + enter
//将其注释掉就error
// @Override
// public void say() {
// }
// @Override
// public void hi() {
// }
}
- 抽象类实现接口,可以不用实现接口的方法
interface IA {
// public abstract void say();
// void say1();
void say(); //修饰符 public protected 默认 private
void hi();
}
//4. 抽象类去实现接口时,可以不实现接口的方法
abstract class Tiger implements IA {
}
- 一个类同时可以实现多个接口
package com.zanedu.interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
}
}
interface IB {
void hi();
}
interface IC {
void say();
}
//一个类可以同时实现多个接口
//pig类 同时 实现两个接口IB IC
class Pig implements IB, IC {
@Override
public void hi() {
}
@Override
public void say() {
}
}
- 接口中的属性,只能是final,而且是 public static final修饰符。比如:int a = 1; 实际上是****public static final int a = 1; (必须初始化)
package com.zanedu.interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
//证明:接口中的属性,是public、static、final 修饰符
System.out.println(IB.n1); //可以调用说明n1就是static
// IB.n1 = 30;//不可以改变说明n1是final的
}
}
interface IB {
//接口中的属性,只能是final的,而且是public、static、final 修饰符
int n1 = 10; //等价于 public static final int n1 = 10;
void hi();
}
interface IC {
void say();
}
- 不能改变说明是final
- 只能是public
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口
interface A extends B, C { }
interface IB {
//接口中的属性,只能是final的,而且是public、static、final 修饰符
int n1 = 10; //等价于 public static final int n1 = 10;
void hi();
}
interface IC {
void say();
}
//接口不能继承其他的类,但是可以继承多个别的接口
interface ID extends IB, IC {
}
- 接口的访问修饰符,只能是 public 和 默认,这点和类的修饰符是一样的
//接口的修饰符只能是public 和 默认,这点和类的修饰符是一样的】
interface IE {}
实现接口 VS 继承类
继承就比如是小猴子继承父类大猴子的能力,会爬树
接口就比如是小猴子通过学习鱼儿,可以游泳
package com.zanedu.interface_;
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("悟空");
littleMonkey.climbing();
littleMonkey.swiming();
littleMonkey.flying();
}
}
//接口
interface Fishable {
void swiming();
}
interface Birdable {
void 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;
}
}
//继承
//小结:当子类继承了父类,就自动拥有了父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式来扩展
// 可以理解 实现接口 是对Java单继承的补充
class LittleMonkey extends Monkey implements Fishable, Birdable{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swiming() {
System.out.println(getName() + "通过学习,可以像鱼儿一样游泳");
}
@Override
public void flying() {
System.out.println(getName() + "通过学习,可以像鸟儿一样飞翔");
}
}
- 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法,即更加的灵活
- 接口比继承更加灵活
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系
- 接口在一定程度上实现代码解耦(即:接口规范性 + 动态绑定机制)
接口的多态特性
- 多态参数
在前面的Usb接口案例中,UsbInterface usb,既可以接受手机对象,又可以接受相机对象,就体现了接口多态(接口引用可以指向实现了接口的类的对象)
package com.zanedu.interface_;
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 if01,可以指向实现了IF接口的类的对象实例
IF if01 = new Monster();
if01 = new Car();
//继承体现的多态
//父类类型的变量 a 可以指向继承AAAA的子类的对象实例
AAAA aaaa = new BBBB();
aaaa = new CCCC();
}
}
interface IF {}
class Monster implements IF {}
class Car implements IF {}
class AAAA {}
class BBBB extends AAAA {}
class CCCC extends AAAA {}
- 多态数组
案例:给Usb数组中,存放Phone和相机对象,Phne类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call
package com.zanedu.interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
//多态数组 -> 接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new MyCamera();
usbs[1] = new MyPhone();
for (int i = 0; i < usbs.length; i++) {
usbs[i].work(); //动态绑定
//和前面一样,我们仍然需要进行类型的向下转型 - 为了访问到call方法
//instanceof判断的是运行类型
if (usbs[i] instanceof MyPhone) { //判断它的运行类型是 MyPhone
((MyPhone) usbs[i]).call();
}
}
}
}
interface Usb {
void work();
}
class MyPhone implements Usb {
public void call() {
System.out.println("手机可以打电话");
}
@Override
public void work() {
System.out.println("手机工作中");
}
}
class MyCamera implements Usb {
@Override
public void work() {
System.out.println("相机工作中");
}
}
3, 接口存在多态传递现象
package com.zanedu.interface_;
/**
* 演示多态传递现象
*/
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() {
;
}
}
练习题
package com.zanedu.interface_;
public class InterfaceExercise {
}
interface A1 {
int x = 1;
}
class B1 {
int x = 0;
}
class C1 extends B1 implements A1 {
public void pX() {
//System.out.println(x); //error 原因不明确x
//可以明确的指定x
//访问接口的x就是用A1.x
//访问父类的x就是用super.x
System.out.println(A1.x);
System.out.println(super.x);
}
public static void main(String[] args) {
new C1().pX();
}
}
内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构**,被嵌套的类称为内部类,嵌套的其他类的类称为外部类,是我们类的第五大成员。内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系**
- 基本语法
class Outer { //外部类
class Inner { //内部类
}
}
class Other { //外部其他类
}
内部类的分类(四种)
定义在外部类局部位置上(比如方法内/代码块内):
- 局部内部类(有内名)
- 匿名内部类(没有类名)
定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类的使用
说明:
- 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
package com.zanedu.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass { //外部其他类
public static void main(String[] args) {
}
}
class Outer02 { //外部类
private int n1 = 100;
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
class Inner02 { //局部内部类 (本质仍然是一个类)
}
}
{//代码块
class Inner03 {
}
}
}
- 可以直接访问外部类的所有成员,包含私有的
class Outer02 { //外部类
private int n1 = 100;
private void m2() { } //私有方法
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
class Inner02 { //局部内部类 (本质仍然是一个类)
//2. 可以直接访问外部类的所有成员,包括私有的
private int n1 = 800;
public void f1() {
System.out.println("n1=" + n1);
m2();
}
}
}
}
- 不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用final修饰,因为局部变量也可以使用final
class Outer02 { //外部类
private int n1 = 100;
private void m2() { } //私有方法
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
//3. 不能添加访问修饰符,但是可以用final修饰
final class Inner02 { //局部内部类 (本质仍然是一个类)
//2. 可以直接访问外部类的所有成员,包括私有的
private int n1 = 800;
public void f1() {
System.out.println("n1=" + n1);
m2();
}
}
}
}
- 补充:被final修饰的类不能被继承
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类 – 访问 —> 外部类的成员【访问方式:直接访问】
- 外部类 – 访问 —> 局部内部类的成员【访问方式:创建对象再访问(注意:必须在作用域内)】
package com.zanedu.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass { //外部其他类
public static void main(String[] args) {
//演示
Outer02 outer02 = new Outer02();
outer02.m1();
}
}
class Outer02 { //外部类
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
} //私有方法
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
//3. 不能添加访问修饰符,但是可以用final修饰
//4. 作用域:仅仅在定义它的方法或代码块中
final class Inner02 { //局部内部类 (本质仍然是一个类)
//2. 可以直接访问外部类的所有成员,包括私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,如下:外部类n1 和 m2()
System.out.println("n1=" + n1);
m2();
}
}
//6. 外部类在方法中,可以创建Inner02的对象,然后调用方法
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
- 外部其他类 – 不能访问 —> 局部内部类(因为局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.zanedu.innerclass;
/**
* 演示局部内部类的使用
*/
public class LocalInnerClass { //外部其他类
public static void main(String[] args) {
//演示
Outer02 outer02 = new Outer02();
System.out.println("outer02的hashCode=" + outer02.hashCode());
outer02.m1();
}
}
class Outer02 { //外部类
private int n1 = 100;
private void m2() {
System.out.println("Outer02 m2()");
} //私有方法
public void m1() { //方法
//1. 局部内部类是定义在外部类的局部位置,通常在方法
//3. 不能添加访问修饰符,但是可以用final修饰
//4. 作用域:仅仅在定义它的方法或代码块中
final class Inner02 { //局部内部类 (本质仍然是一个类)
//2. 可以直接访问外部类的所有成员,包括私有的
private int n1 = 800;
public void f1() {
//5. 局部内部类可以直接访问外部类的成员,如下:外部类n1 和 m2()
//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则
// 如果相访问外部类的成员,使用(外部类名.this.成员)去访问
//解读:Outer02.this 本质就是外部类的对象,即哪个对象调用了m1方法,那么Outer02.this就是哪个对象
System.out.println("n1=" + n1);
System.out.println("外部类的n1=" + Outer02.this.n1);
System.out.println("Outer02.this hashCode=" + Outer02.this.hashCode());
m2();
}
}
//6. 外部类在方法中,可以创建Inner02的对象,然后调用方法
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
匿名内部类的使用
说明:
- 本质还是类
- 内部类
- 该类没有名字
- 同时还是一个对象
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
基本语法:
new 类或接口(参数列表) {
类体
};
- 接口
package com.zanedu.innerclass;
/**
* 演示匿名内部类的使用
*/
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的运行类型 就是匿名内部类 XXX ==> Outer04$1
我们看底层 - 名字看不到,底层会分配 即 类名 + $
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
//7. JDK底层在创建匿名内部类 Outer04$1,立马就创建了 Outer04$1实例,并且把地址返回给 tiger
//8. 匿名内部类使用一次,就不能在使用了 ,但是对象可以反复调用,只不过匿名内部类没了
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
};
System.out.println("tiger的运行类型=" + tiger.getClass());
tiger.cry();
// IA tiger = new Tiger();
// tiger.cry();
}
}
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("狗叫唤...");
// }
//}
- 运行类型用getClass方法获取
- 类
package com.zanedu.innerclass;
/**
* 演示匿名内部类的使用
*/
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
匿名内部类
class Outer04$2 extends Father {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2的对象
//5. 注意("jack")参数列表会传递给构造器
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father对象的运行类型=" + father.getClass());//Outer04$2
father.test();
//基于抽象类的匿名内部类
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
interface IA { //接口
public void cry();
}
//老方法
//class Tiger implements IA {
// @Override
// public void cry() {
// System.out.println("老虎叫唤...");
// }
//}
//class Dog implements IA {
// @Override
// public void cry() {
// System.out.println("狗叫唤...");
// }
//}
class Father { //类
public Father(String name) { //构造器
System.out.println("接收到name=" + name);
}
public void test() { //方法
}
}
abstract class Animal {
abstract void eat();
}
- 基于抽象类的匿名内部类
- 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征
package com.zanedu.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
System.out.println("outer05的hashCode=" + outer05.hashCode());
}
}
class Outer05 {
private int n1 = 99;
public void f1() {
//创建一个基于类的匿名内部类
Person p = new Person() {
private int n1 = 88;
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法 n1=" + n1);
}
};
p.hi();//动态绑定,运行类型是 Outer05$1
//也可以直接调用,因为匿名内部类本身也是返回对象
// class 匿名内部类 extends Person {}
new Person() {
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法,哈哈哈...");
}
@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);
}
}//抽象类/接口...
- 可以直接访问外部类的所有成员,包含私有的
class Outer05 {
private int n1 = 99;
public void f1() {
//创建一个基于类的匿名内部类
Person p = new Person() {
private int n1 = 88;
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法 n1=" + n1);
}
};
p.hi();//动态绑定,运行类型是 Outer05$1
//也可以直接调用,因为匿名内部类本身也是返回对象
// class 匿名内部类 extends Person {}
new Person() {
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法,哈哈哈...");
}
@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);
}
}//抽象类/接口...
- 不能添加访问修饰符,因为它的地位就是一个局部变量
- 作用域:仅仅在定义它的方法或代码块中
- 匿名内部类 – 访问 —> 外部类成员【访问方式:直接访问】
- 外部其他类 – 不能访问 —> 匿名内部类(因为匿名内部类地位是一个局部变量)
- 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.zanedu.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
System.out.println("outer05的hashCode=" + outer05.hashCode());
}
}
class Outer05 {
private int n1 = 99;
public void f1() {
//创建一个基于类的匿名内部类
//不能添加访问修饰符,因为它的低位就是一个局部变量
//作用域:仅仅在定义它的方法或代码块中,并且只会有一次,因为一次使用过后,匿名内部类就消失了
Person p = new Person() {
private int n1 = 88;
@Override
public void hi() {
//可以直接访问外部类的所有成员,包括私有的
/*
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
*/
System.out.println("匿名内部类重写了hi方法 n1=" + n1);
System.out.println("外部类的n1=" + Outer05.this.n1);
//Outer05.this就是调用f1的对象,谁调用f1,那么就是谁
System.out.println("Outer05.this hashCode=" + Outer05.this.hashCode());
}
};
p.hi();//动态绑定,运行类型是 Outer05$1
//也可以直接调用,因为匿名内部类本身也是返回对象
// class 匿名内部类 extends Person {}
new Person() {
@Override
public void hi() {
System.out.println("匿名内部类重写了hi方法,哈哈哈...");
}
@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);
}
}//抽象类/接口...
匿名内部类的最佳实践
- 当作实参直接传递,简洁高效
package com.zanedu.innerclass;
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("这是一副名画..");
}
}
- 题目
- 有一个铃声接口Bell,里面有个ring方法
- 有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型
- 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
- 再传入另一个匿名内部类(对象),打印:小伙伴上课了
package com.zanedu.innerclass;
/**
* 需求:
* 1. 有一个铃声接口Bell,里面有个ring方法
* 2. 有一个手机类Cellphone,具有闹钟功能alarmClock,参数是Bell类型
* 3. 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
* 4. 再传入另一个匿名内部类(对象),打印:小伙伴上课了
*/
public class InnerClassExercise02 {
public static void main(String[] args) {
//测试
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.ring();//动态绑定
}
}
成员内部类的使用
- 说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
- 可以直接访问外部类的所有成员,包括私有的
class Outer08 {
private int n1 = 10;
public String name = "张三";
public void hi() {
System.out.println("hi() 方法");
}
class Inner08 { //成员内部类
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n1 = " + n1 + " name = " + name);
}
}
//写方法
public void t1() {
//使用成员内部类
Inner08 inner08 = new Inner08();
inner08.say();
}
}
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
//1. 注意:成员内部类,是定义在外部类的成员位置上
//2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08 { //成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n1 = " + n1 + " name = " + name);
}
}
- 作用域:和外部类的其他成员一样,为整个类体。比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法
- 成员内部类 – 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】
class Outer08 {
private int n1 = 10;
public String name = "张三";
public void hi() {
System.out.println("hi() 方法");
}
//1. 注意:成员内部类,是定义在外部类的成员位置上
//2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08 { //成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n1 = " + n1 + " name = " + name);
hi();
}
}
//写方法
public void t1() {
//使用成员内部类
Inner08 inner08 = new Inner08();
inner08.say();
}
}
- 外部类 – 访问 —> 成员内部类【访问方式:创建对象再访问】
class Outer08 {
private int n1 = 10;
public String name = "张三";
public void hi() {
System.out.println("hi() 方法");
}
//1. 注意:成员内部类,是定义在外部类的成员位置上
//2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08 { //成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n1 = " + n1 + " name = " + name);
hi();
}
}
//方法返回一个Inner08实例
public Inner08 getInner08Instance() {
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
- 外部其他类 – 访问 —> 成员内部类
package com.zanedu.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式
//解读
//第一种方式
//outer08.new Inner08(); 相当于把new Inner08() 当作 outer08的成员
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
//第二种方式 - 在外部类中编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
public void hi() {
System.out.println("hi() 方法");
}
//1. 注意:成员内部类,是定义在外部类的成员位置上
//2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08 { //成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
System.out.println("n1 = " + n1 + " name = " + name);
hi();
}
}
//方法返回一个Inner08实例
public Inner08 getInner08Instance() {
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
package com.zanedu.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.t1();
//外部其他类,使用成员内部类的三种方式
//解读
//第一种方式
//outer08.new Inner08(); 相当于把new Inner08() 当作 outer08的成员
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
//第二种方式 - 在外部类中编写一个方法,可以返回 Inner08对象
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
inner08Instance.say();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
public void hi() {
System.out.println("hi() 方法");
}
//1. 注意:成员内部类,是定义在外部类的成员位置上
//2. 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
public class Inner08 { //成员内部类
private double sal = 99.9;
private int n1 = 66;
public void say() {
//1. 可以直接访问外部类的所有成员,包括私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则
// 可以通过外部类名.this.属性 来访问外部类的成员
System.out.println("n1 = " + n1 + " name = " + name);
System.out.println("Outer08 n1 = " + Outer08.this.n1);
hi();
}
}
//方法返回一个Inner08实例
public Inner08 getInner08Instance() {
return new Inner08();
}
//写方法
public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
Inner08 inner08 = new Inner08();
inner08.say();
System.out.println(inner08.sal);
}
}
静态内部类的使用
- 说明:静态内部类是定义在外部类的成员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
- 作用域:同其他的成员,为整个类体
class Outer10 { //外部类
private int n1 = 10;
private static String name = "张三";
//Inner10 就是一个静态内部类
//1. 放在外部类的成员位置
//2. 是用static修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
//5. 作用域:同其他的成员,为整个类体
public static class Inner10 {
private static String name = "zan";
public void say() {
System.out.println(name);
}
}
public void m1() {
Inner10 inner10 = new Inner10();
inner10.say();
}
}
- 静态内部类 – 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
- 外部类 – 访问 —> 静态内部类【访问方式:创建对象再访问】
private static void cry() {}
//Inner10 就是一个静态内部类
//1. 放在外部类的成员位置
//2. 是用static修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
//5. 作用域:同其他的成员,为整个类体
public static class Inner10 {
private static String name = "zan";
public void say() {
System.out.println(name);
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
//如果想访问外部类的成员,则可以使用 (外部类名.成员)去访问
System.out.println(Outer10.name);
cry();
}
}
- 外部其他类 --访问 —> 静态内部类
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
package com.zanedu.innerclass;
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();
Outer10.Inner10 inner10_ = Outer10.getInner10_();
System.out.println("*******");
inner10_.say();
}
}
class Outer10 { //外部类
private int n1 = 10;
private static String name = "张三";
private static void cry() {}
//Inner10 就是一个静态内部类
//1. 放在外部类的成员位置
//2. 是用static修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public protected 默认 private),因为它的地位就是一个成员
//5. 作用域:同其他的成员,为整个类体
public static class Inner10 {
private static String name = "zan";
public void say() {
System.out.println(name);
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则
//如果想访问外部类的成员,则可以使用 (外部类名.成员)去访问
System.out.println(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();
}
}
测试题
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();
Inner r = t.new Inner(); //5
System.out.println(r.a); //5
}
}