第九章、面向对象编程(高级部分)
9.1、类变量和类方法(P374~P382)
9.1.1、类变量引出(P374)
-
类变量 - 提出问题
有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在有多少人在玩?编写程序解决。
-
传统的方法来解决
思路:
1、在main方法中定义一个变量count
2、当一个小孩加入堆雪人后count++,最后count就记录了有多少个小孩在玩堆雪人
package com.hspedu.static_;
public class ChildGame {
public static void main(String[] args) {
int count = 0;
Child child = new Child("Mike");
child.join();
count++;
Child child1 = new Child("John");
child1.join();
count++;
Child tom = new Child("Tom");
tom.join();
count++;
System.out.println("共有" + count + "个小孩加入了堆雪人");
}
}
class Child {
public String name;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了堆雪人");
}
}
输出结果为:
Mike加入了堆雪人
John加入了堆雪人
Tom加入了堆雪人
共有3个小孩加入了堆雪人
-
问题分析
1、count是独立于对象的变量,很尴尬
2、以后我们访问count很麻烦
3、因此,我们引出 类变量/静态变量
9.1.2、类变量快速入门(P375)
通过上面问题思考:如果设计一个int count表示总人数,我们在创建一个小孩时,就把count加1,并且count是所有对象共享的就ok了,我们使用类变量来解决。
-
改善上面代码
package com.hspedu.static_;
public class ChildGame {
public static void main(String[] args) {
//int count = 0;
Child child = new Child("Mike");
child.join();
//count++;
child.count++;
Child child1 = new Child("John");
child1.join();
//count++;
child1.count++;
Child tom = new Child("Tom");
tom.join();
//count++;
tom.count++;
System.out.println("共有" + Child.count + "个小孩加入了堆雪人");
//下面代码会输出什么结果
System.out.println("共有" + child.count + "个小孩加入了堆雪人");
System.out.println("共有" + child1.count + "个小孩加入了堆雪人");
System.out.println("共有" + tom.count + "个小孩加入了堆雪人");
}
}
class Child {
//定义一个类变量(静态变量 static)count
//该变量最大的特点就是会被Child 类的所有的对象实例共享
public static int count = 0;
public String name;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了堆雪人");
}
}
输出结果为:
Mike加入了堆雪人
John加入了堆雪人
Tom加入了堆雪人
共有3个小孩加入了堆雪人
共有3个小孩加入了堆雪人
共有3个小孩加入了堆雪人
共有3个小孩加入了堆雪人
9.1.3、类变量内存剖析(P376)
①静态变量被对象共享
②因此不影响对静态变量的使用
在jdk7或者8以前,静态变量是放在方法区的静态域中的;在jdk8以后是放在堆里面的,在对堆里面会通过反射机制,会加载class对象,在对象最后,会把静态变量放在class对象中
9.1.4、类变量定义访问(P377)
-
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。
-
如何定义类变量
定义语法:
访问修饰符 static 数据类型 变量名;//[推荐这种类型]
static 访问修饰符 数据类型 变量名;
-
如何访问类变量
类名.类变量名;//[推荐使用]
//或者
对象名.类变量名;
静态变量的访问修饰符的访问权限和范围与普通属性是一样的。
代码演示:
package com.hspedu.static_;
public class VisitStatic {
public static void main(String[] args) {
//类名.类变量名;//[推荐使用]
//说明:类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问
System.out.println(AAA.str);
System.out.println(AAA.getString_());
//对象名.类变量名;
AAA aaa = new AAA();
System.out.println(aaa.str);
System.out.println(aaa.getString_());
}
}
class AAA{
public static String str = "韩顺平教育";
private static String string_ = "我我我";
public static String getString_() {
return string_;
}
public static void setString_(String string_) {
AAA.string_ = string_;
}
}
输出结果为:
韩顺平教育
我我我
韩顺平教育
我我我
9.1.5、类变量使用细节(P378)
-
类变量使用注意事项和细节讨论
1、什么时候需要用类变量?
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学术类,统计所有学生共交多少钱。Student(name,static fee)
2、类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的;实例变量是每个对象独享的。
3、加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4、类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但Java设计者推荐我们使用 类名.类变量名 形式访问[前提是 满足访问修饰符的访问权限和范围]。
5、实例变量不能通过 类名.类变量名 方式访问
6、类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
7、类变量的生命周期是随着类的加载开始,随着类消亡而销毁。
9.1.6、类方法快速入门(P379)
-
类方法基本介绍
类方法也叫静态方法。
创建语法如下:
访问修饰符 static 数据返回类型 方法名(){}//[推荐]
static 访问修饰符 数据返回类型 方法名(){}
-
类方法的调用
调用语法如下[前提是 满足访问修饰符的访问权限和范围]:
类名.类方法名;
//或者
对象名.类方法名
-
类方法的使用
如:统计学费总和
代码实现:
package com.hspedu.static_;
public class StaticMethod {
public static void main(String[] args) {
//学生1的费用
Stu mary = new Stu("Mary", 100);
System.out.println(mary);
mary.payFee(mary.getStudentFee());//调用方法1
//Stu.payFee(mary.getStudentFee());//调用方法2
//学生2的费用
Stu john = new Stu("John", 200);
System.out.println(john);
john.payFee(john.getStudentFee());//调用方法1
//Stu.payFee(john.getStudentFee());//调用方法2
//查询学费总和
Stu.showFee();
//下面输出结果为
mary.showFee();
}
}
class Stu {
private String name;//普通成员
private double studentFee;//记录单个学生费用
//定义一个静态变量,来累计学生的学费
private static double studentSumFee = 0;
public Stu(String name, double studentFee) {
this.name = name;
this.studentFee = studentFee;
}
//
public static void payFee(double fee) {
Stu.studentSumFee += fee;//累计和
}
public static void showFee() {
System.out.println("总学费为:" + Stu.studentSumFee);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getStudentFee() {
return studentFee;
}
public void setStudentFee(double studentFee) {
this.studentFee = studentFee;
}
@Override
public String toString() {
return "Stu{" +
"name='" + name + '\'' +
", studentFee=" + studentFee +
'}';
}
}
输出结果为:
Stu{name='Mary', studentFee=100.0}
Stu{name='John', studentFee=200.0}
总学费为:300.0
总学费为:300.0
9.1.7、类方法最佳实践(P380)
如果我们希望不创建实例方法,也可以调用某个方法(即当作工具来使用);这时,把方法组成静态方法是非常合适的。
如:
package com.hspedu.static_;
public class StaticMethod {
public static void main(String[] args) {
//调用自己的工具
System.out.println(MyTools.calSum(20, 30));
}
}
class MyTools {
public static double calSum(double num1, double num2) {
return num1 + num2;
}
}
输出结果为:
50.0
小结:
在程序员实际开发中,往往会将一些通用的方法,设计成静态方法,这样我们就不需要创建对象就可以使用;比如打印一维数组,冒泡排序,完成某个计算任务等等。
9.1.8、类方法注意事项(P381)
(1)类方法和普通方法都是随着类的加载而加载的,将结构信息存储在方法区。
(2)类方法可以通过类名调用,也可以通过对象名调用。
(3)普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
(4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法可以。
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
}
}
class WWW {
private int num = 0;
public static void B() {
//(4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法可以。
System.out.println(this.num);//这里直接报错
}
}
(5)类方法(静态方法)中,只能访问 静态变量 或 静态方法。
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
}
}
class WWW {
private int num1 = 0;
private static int num2 = 1;
public void A() {
}
public static void B() {
//(4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法可以。
//System.out.println(this.num);//这里直接报错
}
public static void C() {
//(5)类方法(静态方法)中,只能访问 静态变量 或 静态方法。
//静态方法只能访问静态成员
System.out.println(num2);//正确
System.out.println(WWW.num2);//正确
//System.out.println(this.num2);//错误
B();//正确
WWW.B();//正确
//A();//错误,A()方法为非静态方法
}
}
(6)普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
}
}
class WWW {
private int num1 = 0;
private static int num2 = 1;
public void A() {
}
public static void B() {
//(4)类方法中不允许使用和对象有关的关键字,比如this和super。普通方法可以。
//System.out.println(this.num);//这里直接报错
}
public static void C() {
//(5)类方法(静态方法)中,只能访问 静态变量 或 静态方法。
//静态方法只能访问静态成员
System.out.println(num2);//正确
System.out.println(WWW.num2);//正确
//System.out.println(this.num2);//错误
B();//正确
WWW.B();//正确
//A();//错误,A()方法为非静态方法
}
//(6)普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
public void DD() {
//非静态成员
System.out.println(num1);
A();
//静态成员
System.out.println(num2);
B();
C();
}
}
小结:
静态的方法只能访问静态的成员;非静态的方法,既可以访问静态成员,也可以访问非静态成员。
9.1.9、类成员课堂练习(P382)
1、下面代码输出结果是什么?
main方法:
package com.hspedu.static_;
public class StaticExercise01 {
public static void main(String[] args) {
new Test().count();
new Test().count();
System.out.println(Test.count);
}
}
Test类:
package com.hspedu.static_;
public class Test {
static int count = 9;
public void count() {
System.out.println("count = " + count++);
}
}
输出结果为:
count = 9
count = 10
11
2、看下面代码有没有错误,如果有就注释,看看输出什么。
package com.hspedu.static_;
public class StaticExercise02 {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson());//0
Person person = new Person();
System.out.println("Number of total is " + person.getTotalPerson());//1
System.out.println("Number of total is " + Person.getTotalPerson());//1
}
}
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++;
return total;
}
public Person() {
total++;
id = total;
}
}
输出结果为:
Number of total is 0
Number of total is 1
Number of total is 1
3、看下面代码有没有错误,如果有就注释,看看total结果为多少。
package com.hspedu.static_;
public class StaticExercise03 {
public static void main(String[] args) {
Persons.setTotalPersons(3);
new Persons();
System.out.println(Persons.getTotal());
}
}
class Persons {
private int id;
private static int total = 0;
public static void setTotalPersons(int total) {
//this.total = total;//想把形参里的total赋值给静态变量total,这种方法是错误的,下面这种方法正确
Persons.total = total;
}
public Persons() {
total++;
id = total;
}
public static int getTotal() {
return total;
}
public static void setTotal(int total) {
Persons.total = total;
}
}
输出结果为:
4
9.2、理解main方法语法(P383~P385)
9.2.1、main语法说明(P383)
解释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 waste {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i + 1) + "个参数" + args[i]);
}
}
}
输出结果为:(下面图片的A,B,C,对应着上面图片的第一个参数,第二个参数,第三个参数)
9.2.2、main特别说明(P384)
1、在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2、但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
package com.hspedu.main_;
import sun.applet.Main;
public class Main01 {
private static int num1 = 999;
private int num2 = 222;
public static void PrintNum1() {
System.out.println("静态变量num1 = " + num1);
//System.out.println("非静态变量num2 = " + num2);//会报错
}
public void PrintNum2() {
System.out.println("静态变量num1 = " + num1);
System.out.println("非静态变量num2 = " + num2);
}
public static void main(String[] args) {
//静态成员
System.out.println(num1);
PrintNum1();
//非静态成员
//System.out.println(num2);//会报错
//PrintNum2();//会报错
//正确调用非静态成员
Main01 main01 = new Main01();
System.out.println(main01.num2);
main01.PrintNum2();
}
}
输出结果为:
999
静态变量num1 = 999
222
静态变量num1 = 999
非静态变量num2 = 222
9.2.3、main动态传值(P385)
先运行下面程序,什么都不会输出
package com.hspedu.main_;
public class Main02 {
public static void main(String[] args) {
//先运行一下程序
//快点 上号 来来来 我 你 他
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
截图1
截图2
最后再次运行上面代码,输出结果为:
快点
上号
来来来
我
你
他
9.3、代码块(P386~391)
9.3.1、代码块快速入门(P386)
-
基本介绍
代码化块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。
-
基本语法
[修饰符]{
代码
};
说明注意:
1、修饰符可以不写;要写的话,也只能写static
2、代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
3、逻辑语句可以分为任何逻辑语句(输入,输出、方法调用、循环、判断等)
4、最后的 " ; " 可以写上,也可以省略
-
代码块的好处和案例演示
1、相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2、场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的复用性。
package com.hspedu.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
new Movie("成龙历险记");
System.out.println("-----------------------------------");//分割线
new Movie("喜羊羊与灰太狼", 20);
System.out.println("-----------------------------------");//分割线
new Movie("果宝特攻", 30, "陈奋、王巍");
}
}
class Movie {
private String name;
private double price;
private String director;
//创建3个构造器
//在每个构造器中添加相同的语句
public 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("电影屏幕打开...");
// System.out.println("广告开始...");
// System.out.println("电影正式开始播放...");
this.name = name;
this.price = price;
this.director = director;
}
// (1)上面三个构造器都有相同的语句,把他们注释
// (2)这样看起来比较冗余
// (3)这时我们可以把相同的语句,放到一个代码块中
// (4)这样我们不管先调用哪个构造器,都会先调用代码块中的内容
// (5)运行程序后,会发现,代码块调用的优先级高于构造器
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始播放...");
};//这里分号写不写均可
}
输出结果为:
电影屏幕打开...
广告开始...
电影正式开始播放...
-----------------------------------
电影屏幕打开...
广告开始...
电影正式开始播放...
-----------------------------------
电影屏幕打开...
广告开始...
电影正式开始播放...
9.3.2、代码块使用细节_1(P387)
-
代码块使用注意事项和细节讨论
1、static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
2、类什么时候被加载[重点]
①创建对象实例时(new)
②创建子类对象实例,父类也会被加载
③使用类的静态成员时(静态属性,静态方法)
案例演示:
package com.hspedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//static代码块,它随着类的加载而执行,并且只会执行一次。
//类被加载有3种情况
//①创建对象实例时(new)
AA aa = new AA();//注释掉这一行,输出结果不变
//②创建子类对象实例,父类也会被加载
BB bb = new BB();//再注释掉这一行,输出结果不变
//③使用类的静态成员时(静态属性,静态方法)
System.out.println(BB.n1);
System.out.println(BB.n2);
}
}
class AA {
public static int n2 = 666;
static {
System.out.println("AA 的代码块被调用");
}
}
class BB extends AA {
public static int n1 = 99;
static {
System.out.println("BB 的代码块被调用");
}
}
输出结果为:
AA 的代码块被调用
BB 的代码块被调用
99
666
3、①普通的代码块,在创建对象实例时,会被隐式的调用;被创建一次,就会调用一次。
package com.hspedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//2、static代码块,它随着类的加载而执行,并且只会执行一次。
//类被加载有3种情况
//①创建对象实例时(new)
AA aa = new AA();//注释掉这一行,输出结果不变
//②创建子类对象实例,父类也会被加载
BB bb = new BB();//再注释掉这一行,输出结果不变
//③使用类的静态成员时(静态属性,静态方法)
System.out.println(BB.n1);
System.out.println(BB.n2);
System.out.println("-----------------------------------");//分割线
//3、普通的代码块,在创建对象实例时,会被隐式的调用;被创建一次,就会调用一次。
new DD();
new DD();//这里静态代码块被调用一次,普通代码块被调用两次
// 如果只是使用类的静态成员时,普通代码块并不会执行。
}
}
class AA {
public static int n2 = 666;
static {
System.out.println("AA 的代码块被调用");
}
}
class BB extends AA {
public static int n1 = 99;
static {
System.out.println("BB 的代码块被调用");
}
}
class DD {
public static int num3 = 123;
static {
System.out.println("静态代码块被调用");
}
{
System.out.println("普通代码块被调用");
}
}
输出结果为:
AA 的代码块被调用
BB 的代码块被调用
99
666
-----------------------------------
静态代码块被调用
普通代码块被调用
普通代码块被调用
②如果只是使用类的静态成员时,普通代码块并不会执行。
package com.hspedu.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//2、static代码块,它随着类的加载而执行,并且只会执行一次。
//类被加载有3种情况
//①创建对象实例时(new)
AA aa = new AA();//注释掉这一行,输出结果不变
//②创建子类对象实例,父类也会被加载
BB bb = new BB();//再注释掉这一行,输出结果不变
//③使用类的静态成员时(静态属性,静态方法)
System.out.println(BB.n1);
System.out.println(BB.n2);
System.out.println("-----------------------------------");//分割线
//3、普通的代码块,在创建对象实例时,会被隐式的调用;被创建一次,就会调用一次。
// new DD();
// new DD();//这里静态代码块被调用一次,普通代码块被调用两次
// 如果只是使用类的静态成员时,普通代码块并不会执行。
System.out.println(DD.num3);
DD.PrintDD();//还是不会调用普通代码块
}
}
class AA {
public static int n2 = 666;
static {
System.out.println("AA 的代码块被调用");
}
}
class BB extends AA {
public static int n1 = 99;
static {
System.out.println("BB 的代码块被调用");
}
}
class DD {
public static int num3 = 123;
static {
System.out.println("静态代码块被调用");
}
{
System.out.println("普通代码块被调用");
}
public static void PrintDD() {
System.out.println("DD中的PrintDD方法被调用");
}
}
输出结果为:
AA 的代码块被调用
BB 的代码块被调用
99
666
-----------------------------------
静态代码块被调用
123
DD中的PrintDD方法被调用
9.3.2、代码使用细节_2(P388)
4、创建对象时,在一个类 调用的顺序是①②③(重点,难点):
①调用静态代码块和静态属性初始化
静态代码块和静态属性初始化调用的优先级一样;如果有多个代码块和多个静态变量初始化,则按他们定义的顺序调用。
package com.hspedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
new A();
}
}
class A {
private static int n1 = getN1();
static {
System.out.println("A类的代码块被调用");
}
public static int getN1() {
System.out.println("A类的getN1()方法被调用");
return 10;
}
public static void setN1(int n1) {
A.n1 = n1;
}
}
输出结果为:
A类的getN1()方法被调用
A类的代码块被调用
②调用普通代码块和普通属性的初始化
普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用。
package com.hspedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
new A();
}
}
class A {
private int n2 = getN2();
{
System.out.println("A类的普通代码块被调用");
}
private static int n1 = getN1();
static {
System.out.println("A类的静态代码块被调用");
}
public static int getN1() {
System.out.println("A类的getN1()方法被调用");
return 10;
}
public static void setN1(int n1) {
A.n1 = n1;
}
public int getN2() {
System.out.println("A类的getN2()被调用");
return 200;
}
}
输出结果为:
A类的getN1()方法被调用
A类的静态代码块被调用
A类的getN2()被调用
A类的普通代码块被调用
③调用构造方法
package com.hspedu.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
new A();
}
}
class A {
private int n2 = getN2();
{
System.out.println("A类的普通代码块被调用");
}
private static int n1 = getN1();
static {
System.out.println("A类的静态代码块被调用");
}
public static int getN1() {
System.out.println("A类的getN1()方法被调用");
return 10;
}
public static void setN1(int n1) {
A.n1 = n1;
}
public int getN2() {
System.out.println("A类的getN2()被调用");
return 200;
}
public A() {
System.out.println("A类的无参构造器被调用");
}
}
输出结果为:
A类的getN1()方法被调用
A类的静态代码块被调用
A类的getN2()被调用
A类的普通代码块被调用
A类的无参构造器被调用
9.3.3、代码使用细节_3(P389)
5、构造器的最前面其实隐含了 super()和调用普通代码块,新写一个类演示,静态相关的代码,属性初始化,在类加载时就执行完毕,因此是优先于构造器和普通代码块执行的。
package com.hspedu.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();
}
}
class AAA {
public AAA() {
//super();
//调用本类的普通代码块
System.out.println("AAA无参构造器被调用");//2
}
{
System.out.println("AAA的普通代码块被调用");//1
}
}
class BBB extends AAA{
public BBB() {
//super();
//调用本类的普通代码块
System.out.println("BBB无参构造器被调用");//4
}
{
System.out.println("BBB的普通代码块被调用");//3
}
}
输出结果为:
AAA的普通代码块被调用
AAA无参构造器被调用
BBB的普通代码块被调用
BBB无参构造器被调用
9.3.4、代码使用细节_4(P390)
6、我们看一下创建一个子类对象时(继承关系),他们的静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序如下:
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④父类的构造方法
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥子类的构造方法//面试题
package com.hspedu.codeblock_;
import com.sun.xml.internal.bind.v2.TODO;
public class CodeBlockDetail04 {
public static void main(String[] args) {
//老师说明
//(1) 进行类的加载
//1.1 先加载 父类 A02 1.2 再加载 B02
//(2) 创建对象
//2.1 从子类的构造器开始
new B02();//对象
}
}
class A02 { //父类
private static int n1 = getVal01();
static {
System.out.println("A02 的一个静态代码块..");//(2)
}
{
System.out.println("A02 的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//构造器
//隐藏
//super()
//普通代码和普通属性的初始化......
System.out.println("A02 的构造器");//(7)
}
}
class B02 extends A02 { //
private static int n3 = getVal03();
static {
System.out.println("B02 的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02 的第一个普通代码块..");//(9)
}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
//一定要慢慢的去品..
public B02() {//构造器
//隐藏了
//super()
//普通代码块和普通属性的初始化...
System.out.println("B02 的构造器");//(10)
}
}
输出结果为:
getVal01
A02 的一个静态代码块..
getVal03
B02 的一个静态代码块..
A02 的第一个普通代码块..
getVal02
A02 的构造器
getVal04
B02 的第一个普通代码块..
B02 的构造器
7、静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
package com.hspedu.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);错误
System.out.println(n2);//ok
//m1();//错误
m2();
}
{
//普通代码块,可以使用任意成员
System.out.println(n1);
System.out.println(n2);//ok
m1();
m2();
}
}
输出结果为:
200
100
200
9.3.5、代码块课堂练习(P391)
1、下面代码输出什么?
package com.hspedu.codeblock_;
public class CodeBlockExercise01 {
public static void main(String[] args) {
System.out.println("total = " + Person.total);//(1)调用静态变量 (5)打印total的值
System.out.println("total = " + Person.total);//(6)打印total的值
}
}
class Person {
public static int total;//(2)先创建静态变量
static {
total = 100;//(3)给total赋值
System.out.println("int static block!");//(4)输出这句话,返回
}
}
输出结果为:
int static block!
total = 100
total = 100
2、下面代码输出什么?
package com.hspedu.codeblock_;
public class CodeBlockExercise02 {
public static void main(String[] args) {
Test test = new Test();
}
}
class Test {
Sample sam1 = new Sample("sam1成员初始化");//(3)调用Sample类有参构造器输出这句话
static Sample sam = new Sample("静态成员sam初始化");//(1)由于为静态成员,先创建,调用Sample类有参构造器先输出这句话
static {
System.out.println("static块执行");//(2)输出这句话
if (sam == null)
System.out.println("sam is null");
}
Test() {
System.out.println("Test默认构造函数被调用");
}
}
class Sample {
Sample(String s) {
System.out.println(s);
}
Sample() {
System.out.println("Sample默认构造函数被调用");
}
}
输出结果为:
静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造函数被调用
9.4、单例设计模式(P392~393)
9.4.1、单例模式饿汉式(P392)
1、静态方法和属性的经典作用
2、设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索
-
什么是单例模式
单例(单个的实例)
1、所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只能提供一个取得其对象实例的方法
2、单例模式有两种方式:①饿汉式②懒汉式
-
单例模式应用实例
演示饿汉式与懒汉式单例模式的实现
步骤如下:
①构造器私有化 =====》防止直接new
②类的内部创建对象
③向外暴露一个静态的公共方法。getInstance,Instance:实例
④饿汉式中有属性被调用,其他静态属性即使不使用,也会创建,占用存储,会造成资源浪费
代码实现:
(饿汉式)
package com.hspedu.codeblock_;
public class SingleTon01 {
public static void main(String[] args) {
GirlFriend girlFriend = GirlFriend.get123();
System.out.println(girlFriend);
GirlFriend girlFriend1 = GirlFriend.get123();
System.out.println(girlFriend1);
System.out.println(girlFriend == girlFriend1);//true,说明为同一个对象
}
}
class GirlFriend {
private String name;
//2、类的内部创建对象
private static GirlFriend gf = new GirlFriend("ggb");
//1、构造器私有化
private GirlFriend(String name) {
this.name = name;
}
//3、向外暴露一个静态的公共方法。
//为了能够在静态方法中,返回 gf 对象,需要将其修饰为static
public static GirlFriend get123() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
输出结果为:
GirlFriend{name='ggb'}
GirlFriend{name='ggb'}
true
9.4.2、单例模式懒汉式(P393)
(2)懒汉式
步骤如下:
①构造器私有化 =====》防止直接new
②类的内部创建对象
③向外暴露一个静态的公共方法。
package com.hspedu.single_;
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.n1);//并不会调用构造器
Cat cat = Cat.getCat();
System.out.println(cat);
}
}
class Cat {
private String name;
public static int n1 = 100;
//②类的内部创建对象
private static Cat cat;
//①构造器私有化
private Cat(String name) {
System.out.println("构造器被调用");
this.name = name;
}
//③向外暴露一个静态的公共方法。
public static Cat getCat() {
if (cat == null) {
cat = new Cat("小白");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
输出结果为:
100
构造器被调用
Cat{name='小白'}
-
饿汉式VS懒汉式
1、二者最主要区别在于创建对象的时机不同:饿汉式实在类加载就创建了对象实例,而懒汉式是在使用时才创建。
2、饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程,再进行完善)
3、饿汉式存在浪费资源的可能。如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了;懒汉式是使用时才创建,就不存在这个问题。
9.5、final关键字(P394~397)
9.5.1、final基本使用(P395)
-
基本介绍 final:最后的,最终的
final 可以修饰类、属性、方法和局部变量
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。 final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
加粗原文链接:浅析Java中的final关键字(详细)_java final关键字-CSDN博客
在某些情况下,程序员可能有以下需求,就会使用到final:
①在不希望类被继承时,可以用final修饰
package com.hspedu.final_;
public class Final01 {
public static void main(String[] args) {
}
}
//如果不希望A类被其他类继承,就加final修饰A类
final class A {
}
//class B extends A {//这里会报错
//
//}
②当不希望父类的某个方法被子类覆盖/重写时,可以用final关键字修饰
package com.hspedu.final_;
public class Final01 {
public static void main(String[] args) {
}
}
//如果不希望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() {//父类加上final,这里就不能重写hi()方法,会报错
// super.hi();
// }
}
③当不希望类的某个属性的值被修改,可以用final修饰
package com.hspedu.final_;
public class Final01 {
public static void main(String[] args) {
// //变量TAX_RATE没有使用final修饰时,可以修改值
// E e = new E();
// e.TAX_RATE = 0.09;//final修饰后,这里会报错
}
}
//如果不希望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() {//父类加上final,这里就不能重写hi()方法,会报错
// super.hi();
// }
}
//③当不希望类的某个属性的值被修改,可以用final修饰
class E {
public final double TAX_RATE = 0.08;
}
④当不希望某个局部变量被修改,可以使用final修饰
package com.hspedu.final_;
public class Final01 {
public static void main(String[] args) {
// //变量TAX_RATE没有使用final修饰时,可以修改值
// E e = new E();
// e.TAX_RATE = 0.09;//final修饰后,这里会报错
}
}
//如果不希望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() {//父类加上final,这里就不能重写hi()方法,会报错
// super.hi();
// }
}
//③当不希望类的某个属性的值被修改,可以用final修饰
class E {
public final double TAX_RATE = 0.08;
}
//④当不希望某个局部变量被修改,可以使用final修饰
class F {
{
final double TAX_RATE = 0.08;
// TAX_RATE = 0.09;//变量名没用final修饰,可以改值,加上final后,就会报错
}
}
9.5.2、final使用细节_1(P396)
-
final使用注意事项和细节讨论
1、final修饰的属性又叫常量,一般 用 XX_XX_XX 来命名
2、final修饰的属性在定义时,必须赋初值,并且以后不能再更改,赋值可以在如下位置之一[选择一个位置赋初值即可]
①定义时:
如: public final double TAX_RATE=0.08;
package com.hspedu.final_;
public class FinalDetail {
}
class AA {
public final double TAX_RATE = 0.08;
}
②在构造器中
package com.hspedu.final_;
public class FinalDetail {
}
class AA {
//public final double TAX_RATE = 0.08;
public final double TAX_RATE2;
public AA() {
TAX_RATE2 = 6.6;
}
}
③在代码块中,修饰的属性不是静态的,不能再静态代码块中赋值
package com.hspedu.final_;
public class FinalDetail {
}
class AA {
//public final double TAX_RATE = 0.08;
// public final double TAX_RATE2;
//
// public AA() {
// TAX_RATE2 = 6.6;
// }
public final double TAX_RATE3;
{
TAX_RATE3 = 8.8;
}
}
3、如果final修饰的属性是静态的,则初始化的位置只能是
①定义时
②在静态代码块 不能在构造器中赋值
package com.hspedu.final_;
public class FinalDetail {
}
class AA {
//public final double TAX_RATE = 0.08;
// public final double TAX_RATE2;
//
// public AA() {
// TAX_RATE2 = 6.6;
// }
public final double TAX_RATE3;
{
TAX_RATE3 = 8.8;
}
}
//3、如果final修饰的属性是静态的,则初始化的位置只能是
//①定义时
//②在静态代码块 不能在构造器中赋值
class CC {
public static final double TAX_RATE;
static {
TAX_RATE = 9.9;
}
}
4、final类中不能继承,但是可以实例化对象(也就是可以在main方法中创建对象)
package com.hspedu.final_;
public class FinalDetail {
public static void main(String[] args) {
//4、final类中不能继承,但是可以实例化对象(也就是可以在main方法中创建对象)
BB bb = new BB();
}
}
class AA {
//public final double TAX_RATE = 0.08;
// public final double TAX_RATE2;
//
// public AA() {
// TAX_RATE2 = 6.6;
// }
public final double TAX_RATE3;
{
TAX_RATE3 = 8.8;
}
}
final class BB{
}
class CC {
//3、如果final修饰的属性是静态的,则初始化的位置只能是
// ①定义时
// ②在静态代码块 不能在构造器中赋值
public static final double TAX_RATE;
static {
TAX_RATE = 9.9;
}
}
5、如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
package com.hspedu.final_;
public class FinalDetail {
public static void main(String[] args) {
//5、如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
DD dd = new DD();
dd.Print();
}
}
class AA {
//public final double TAX_RATE = 0.08;
// public final double TAX_RATE2;
//
// public AA() {
// TAX_RATE2 = 6.6;
// }
public final double TAX_RATE3;
{
TAX_RATE3 = 8.8;
}
}
class CC {
//3、如果final修饰的属性是静态的,则初始化的位置只能是
//• ①定义时
//• ②在静态代码块 不能在构造器中赋值
public static final double TAX_RATE;
static {
TAX_RATE = 9.9;
}
//5、如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
public final void Print() {
System.out.println("CC类的Print方法");
}
}
//5、如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
class DD extends CC{
}
9.5.3、final使用细节_2(P397)
6、一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。(类已经无法被继承,方法也就无法重写)
7、final不能修饰构造方法(即构造器)
8、final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化和处理
package com.hspedu.final_;
public class FinalDetail02 {
public static void main(String[] args) {
System.out.println(AAA.num);
}
}
//8、final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化和处理
class AAA {
public final static int num = 999;
static {
System.out.println("AAA类的静态代码块");
}
public AAA() {
System.out.println("AAA类的无参构造器");
}
}
输出结果为:
999
9、包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
9.5.4、final课堂练习(P398)
1、请编写一个程序,能够计算原型的面积。要求圆周率为 3.14,赋值的位置3个方式都要写一下
package com.hspedu.final_;
//1、请编写一个程序,能够计算原型的面积。要求圆周率为 3.14,赋值的位置3个方式都要写一下
public class FinalExercise01 {
public static void main(String[] args) {
Circle circle = new Circle(2.0);
System.out.println("第一种方法:" + circle.CircleArea());
Circle circle1 = new Circle(3.0);
System.out.println("第二种方法:" + circle1.CircleArea1());
Circle circle2 = new Circle(6.0);
System.out.println("第三种方法:" + circle2.CircleArea2());
}
}
class Circle {
private double radius;
private final double PI = 3.14;//①
private final double PI1;//②
{
PI1 = 3.14;
}
private final double PI2;
public Circle(double radius) {
PI2 = 3.14;
this.radius = radius;
}
public double CircleArea() {
return PI * radius * radius;
}
public double CircleArea1() {
return PI1 * radius * radius;
}
public double CircleArea2() {
return PI2 * radius * radius;
}
}
输出结果为:
第一种方法:12.56
第二种方法:28.259999999999998
第三种方法:113.03999999999999
2、程序阅读题
public class Something {
public int addOne (final int x) {//这里形参没有问题,调用方法时,一定会给x赋值,也就是赋初值
++x;//错误,final修饰后,不能修改值
return x + 1;//正确,没有修改x的值
}
}
9.6、抽象类(P398~402)
9.6.1、抽象类引出(P398)
-
先看一个问题
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关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
package com.hspedu.abstract_;
public class Abstract01 {
public static void main(String[] args) {
}
}
abstract class Animal {
private String name;
// public void eat(){
// System.out.println("不同动物吃不同的食物,没有实际用途");
// }
//思考:这里eat 你实现了这个方法,但是没有什么意义
//即:父类方法不确定性的问题
//===> 考虑将该方法设计为抽象(abstract)方法
//===> 所谓抽象方法就是没有实现的方法
//===> 所谓没有实现就是指,没有方法体
//===> 当一个类中存在抽象方法时,需要将该类声明为abstract类
//===> 一般来说,抽象类会被继承,由其子类来实现抽象方法
abstract public void eat();
}
9.6.2、抽象类细节_1(P399)
-
抽象类的介绍
1、用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
}
2、用abstract关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
3、抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并并实现抽象类
4、抽象类,是考官比较爱问的知识点,在框架和设计模式使用的较多。
-
抽象类使用的注意事项和细节讨论
1、抽象类不能被实例化
package com.hspedu.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
// //①抽象类不能被实例化
// new A();//这里会报错
}
}
abstract class A{
}
2、抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法,还可以有实现的方法
package com.hspedu.abstract_;
public class AbstractDetail01 {
public static void main(String[] args) {
// //1、抽象类不能被实例化
// new A();//这里会报错
}
}
abstract class A {
//2、抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法,还可以有实现的方法
public void hi() {
}
}
3、一旦类包含了abstract方法,则这个类必须声明为abstract
4、abstract只能修饰类和方法,不能修饰属性和其它的。
9.6.2、抽象类细节_2(P400)
5、抽象类可以有任意成员[抽象类本质还是类],比如:非抽象方法(实现的方法)、构造器、静态属性等等
package com.hspedu.abstract_;
public class AbstractDetail02 {
public static void main(String[] args) {
}
}
//5、抽象类可以有任意成员[抽象类本质还是类],比如:非抽象方法(实现的方法)、构造器、静态属性等等
abstract class D {
private int num = 999;
private String name = "Mike";
public void hi() {
System.out.println("hi");
}
public static void hello() {
System.out.println("777");
}
{
System.out.println("123");
}
public D(int num, String name) {
this.num = num;
this.name = name;
}
}
6、抽象方法不能有主体,即不能实现,如图所示
7、如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象(abstract)类
package com.hspedu.abstract_;
public class AbstractDetail02 {
public static void main(String[] args) {
}
}
//5、抽象类可以有任意成员[抽象类本质还是类],比如:非抽象方法(实现的方法)、构造器、静态属性等等
abstract class D {
private int num = 999;
private String name = "Mike";
public void hi() {
System.out.println("hi");
}
public static void hello() {
System.out.println("777");
}
{
System.out.println("123");
}
public D(int num, String name) {
this.num = num;
this.name = name;
}
}
//7、如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象(abstract)类
abstract class E {
public abstract void Print();
}
//①如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,
class G extends E{
@Override
public void Print() {
}
}
//②除非它自己也声明为抽象(abstract)类
abstract class F extends E {
}
8、抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
//8、抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
abstract class H {
abstract private void hell();//报错,去掉private正确
abstract public final void hello();//报错,去掉final正确
abstract public static void hello2();//报错去掉static正确
}
9.6.3、抽象类课堂练习(P401)
1、abstract final class A{} 编译能通过吗
不能,abstract不能与final在一起使用,final不能继承
2、abstract public static void test2(); 编译能通过吗
不能,abstract不能与static 在一起使用,test2无法被重写
3、abstract private void test3();编译能通过吗
不能,abstract不能与private 在一起使用,private 私有方法无法被重写
4、编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示"经理/普通员工 名字 工作中"
main方法:
package com.hspedu.abstract_;
//4、编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。
// 提供必要的构造器和抽象方法:work()。
// 对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
// 请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,
// 实现work(),提示"经理/普通员工 名字 工作中"
public class AbstractExercise01 {
public static void main(String[] args) {
CommonEmployee mike = new CommonEmployee("Mike", 1, 3000);
mike.work();
Manager john = new Manager("John", 2, 5000);
john.setBonus(1000);
john.work();
}
}
父类Employee:
package com.hspedu.abstract_;
//编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。
abstract public class Employee {
private String name;
private int id;
private double salary;
提供必要的构造器和抽象方法:work()。
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public abstract void work();
}
子类Manager:
package com.hspedu.abstract_;
// 对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
// 请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,
// 实现work(),提示"经理/普通员工 名字 工作中"
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;
}
}
子类CommonEmployee:
package com.hspedu.abstract_;
// 请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,
// 实现work(),提示"经理/普通员工 名字 工作中"
public class CommonEmployee extends Employee {
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("普通员工" + super.getName() + "工作中");
}
}
9.6.4、抽象模板模式(P402)
-
最佳实践
需求
①有多个类,完成不同的任务job
②要求统计得到各自完成任务的时间
③编程实现
main方法:
package com.hspedu.abstract_;
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculate();
BB bb = new BB();
bb.calculate();
}
}
父类Template:
package com.hspedu.abstract_;
public abstract class Template {//抽象类-模板设计模式
public abstract void job();//抽象方法
public void calculate() {//实现方法,调用job方法
//得到开始时间
long start = System.currentTimeMillis();
job();//动态绑定机制
//得到结束时间
long end = System.currentTimeMillis();
System.out.println("任务执行时间:" + (end - start));
}
}
子类AA:
package com.hspedu.abstract_;
public class AA extends Template {
@Override
public void job() {
long sum = 0;
for (int i = 0; i < 80000; i++) {
sum *= i;
}
}
}
子类BB:
package com.hspedu.abstract_;
public class BB extends Template{
@Override
public void job() {
long sum = 0;
for (int i = 0; i < 800000; i++) {
sum *= i;
}
}
}
输出结果为:
任务执行时间:1
任务执行时间:3
9.7、接口(P403~412)
9.7.1、接口快速入门(P403)
-
为什么有接口
先看一张图
-
接口快速入门
这样的设计需求在java编程/php/.net/go中也是会大量存在的,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。
main方法:
package com.hspedu.interface_;
public class Interface01 {
public static void main(String[] args) {
//创建手机和相机对象
Phone phone = new Phone();
Camera camera = new Camera();
//创建计算机
Computer computer = new Computer();
computer.work(phone);//把手机接入电脑
System.out.println("==============================");//分割线
computer.work(camera);
}
}
接口UsbInterface:
package com.hspedu.interface_;
public interface UsbInterface {//接口
//接口里面抽象方法可以不写abstract
//规定接口的相关方法
public void start();
public void end();
}
Computer类:
package com.hspedu.interface_;
public class Computer {
//编写一个方法
public void work(UsbInterface usbInterface) {
//通过接口来调用方法
usbInterface.start();
usbInterface.end();
}
}
Camera类:
package com.hspedu.interface_;
public class Camera implements UsbInterface{
@Override
public void start() {
System.out.println("相机开始传输数据...");
}
@Override
public void end() {
System.out.println("相机停止传输数据...");
}
}
Phone类:
package com.hspedu.interface_;
//Phone类实现UsbInterface
//1、Phone类需要实现UsbInterface接口 规定/声明的方法
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始传输数据...");
}
@Override
public void end() {
System.out.println("手机停止传输数据...");
}
}
输出结果为:
手机开始传输数据...
手机停止传输数据...
==============================
相机开始传输数据...
相机停止传输数据...
9.7.2、接口基本介绍(P404)
-
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
语法:
interface 接口名{
//属性
//方法
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}
小结:
1、接口中的方法全部是抽象方法,默认public abstract 修饰,也只能是public abstract修饰,public abstract可以省略
2、接口中的方法有抽象方法(abstract),默认方法(default),静态方法(static)接口中所有方法的访问权限修饰符都是public,也只能是public,public 可以省略
main方法:
package com.hspedu.interface_;
public class Interface02 {
public static void main(String[] args) {
}
}
class AAA implements AInterface {
//必须实现的接口的抽象方法
@Override
public void hi() {
}
@Override
public void hi2() {
}
@Override
public void hello() {
}
}
AInterface接口:
package com.hspedu.interface_;
public interface AInterface {
//属性
public int num = 0;
//方法
//在接口中,抽象方法,可以省略abstract关键字
public void hello();
//从jdk8开始后,可以有默认和public实现方法,需要使用default关键字修饰
default void hi() {
}
default public void hi2() {
}
//从jdk8开始后,可以有静态方法,静态方法不需要重写
public static void staticHi() {
}
}
9.7.3、接口应用场景(P405)
-
深入讨论
对于初学者来讲,理解接口的概念不算太难,难的是不知道什么时候使用接口
应用场景:接口规定方法名称
代码:
main方法:
package com.hspedu.interface_;
public class Interface03 {
public static void main(String[] args) {
MySQL mySQL = new MySQL();
call(mySQL);
System.out.println("=======================");//分割线
Oracle oracle = new Oracle();
call(oracle);
}
public static void call(DBInterface db) {
db.connect();
db.close();
}
}
接口DBInterface:
package com.hspedu.interface_;
public interface DBInterface {
public void connect();
public void close();
}
MySQL类(程序员A写):
package com.hspedu.interface_;
//A程序员写数据库连接。使用接口,这样方法命名就不可更改了
public class MySQL implements DBInterface {
@Override
public void connect() {
System.out.println("连接数据库");
}
@Override
public void close() {
System.out.println("关闭数据库");
}
}
Oracle类(程序员B写):
package com.hspedu.interface_;
public class Oracle implements DBInterface {
@Override
public void connect() {
System.out.println("连接Oracle");
}
@Override
public void close() {
System.out.println("关闭Oracle");
}
}
9.7.4、接口使用细节_1(P406)
-
注意事项
1、接口不能被实例化
package com.hspedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
// //1、接口不能被实例化
// new IA();//这里会报错
}
}
interface IA{}
2、接口中所有的方法是public方法;接口中抽象方法,可以不用abstract修饰
package com.hspedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
// //1、接口不能被实例化
// new IA();//这里会报错
}
}
interface IA {
//2、接口中所有的方法是public方法;接口中抽象方法,可以不用abstract修饰
public abstract void hello1();
public void hello2();
}
3、一个普通类实现接口,就必须将该接口的所有方法都实现。
package com.hspedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
// //1、接口不能被实例化
// new IA();//这里会报错
}
}
interface IA {
//2、接口中所有的方法是public方法;接口中抽象方法,可以不用abstract修饰
public abstract void hello1();
public void hello2();
}
//3、一个普通类实现接口,就必须将该接口的所有方法都实现。
class Cat implements IA{
@Override
public void hello1() {
}
@Override
public void hello2() {
}//在接口名前,摁下alt+enter快捷键,创建方法
}
4、抽象类实现接口,可以不用实现接口的方法
package com.hspedu.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
// //1、接口不能被实例化
// new IA();//这里会报错
}
}
interface IA {
//2、接口中所有的方法是public方法;接口中抽象方法,可以不用abstract修饰
public abstract void hello1();
public void hello2();
}
//3、一个普通类实现接口,就必须将该接口的所有方法都实现。
class Cat implements IA{
@Override
public void hello1() {
}
@Override
public void hello2() {
}//在接口名前,摁下alt+enter快捷键,创建方法
}
//4、抽象类实现接口,可以不用实现接口的方法
abstract class Tiger implements IA{}
9.7.5、接口使用细节_2(P407)
5、一个类可以同时实现多个接口
package com.hspedu.interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
}
}
interface IB {
void Pig();
}
interface IC {
void Cat();
}
//5、一个类可以同时实现多个接口
class Tools implements IB,IC{
@Override
public void Pig() {
}
@Override
public void Cat() {
}
}
6、接口中的属性,只能是final的,而且是public static final修饰符。比如:int a = 1;实际上是public static final int a = 1;(必须初始化)
package com.hspedu.interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
System.out.println(IB.a);//能使用 类名.属性名 调用,证明属性被static修饰
// IB.a = 10;//这里会报错,也就说明变量a被final修饰
}
}
interface IB {
//6、接口中的属性,只能是final的,而且是public static final修饰符。
// 比如:int a = 1;实际上是public static final int a = 1;(必须初始化)
int a = 1;
void Pig();
}
interface IC {
void Cat();
}
//5、一个类可以同时实现多个接口
class Tools implements IB, IC {
@Override
public void Pig() {
}
@Override
public void Cat() {
}
}
7、接口中的属性的访问形式:接口名.属性名
8、接口中不能继承其它的类,但是可以继承多个别的接口
package com.hspedu.interface_;
public class InterfaceDetail02 {
public static void main(String[] args) {
System.out.println(IB.a);//能使用 类名.属性名 调用,证明属性被static修饰
// IB.a = 10;//这里会报错,也就说明变量a被final修饰
}
}
interface IB {
//6、接口中的属性,只能是final的,而且是public static final修饰符。
// 比如:int a = 1;实际上是public static final int a = 1;(必须初始化)
int a = 1;
void Pig();
}
interface IC {
void Cat();
}
//8、接口中不能继承其它的类,但是可以继承多个别的接口
interface ID extends IB, IC {
}
//5、一个类可以同时实现多个接口
class Tools implements IB, IC {
@Override
public void Pig() {
}
@Override
public void Cat() {
}
}
9、接口的修饰符只能是public和默认,这点和类的修饰符是一样的。
9.7.6、接口的课堂练习(P408)
看下面代码是否正确?如果正确,输出什么?
package com.hspedu.interface_;
public class InterfaceExercise01 {
public static void main(String[] args) {
B b = new B();
//B类实现了AA接口,所以可以使用属性a
System.out.println(b.a);
System.out.println(B.a);
System.out.println(AA.a);
}
}
interface AA {
int a = 23;//等价于public static final int a = 23;
}
class B implements AA {
}
输出结果为:
23
23
23
9.7.7、接口VS继承(P409)
package com.hspedu.interface_;
public class Extends_VS_Interface {
public static void main(String[] args) {
littleMonkey wuKong = new littleMonkey("悟空");
wuKong.clime();
wuKong.fly();
wuKong.swim();
}
}
interface fishAble {
void swim();
}
interface birdAble {
void fly();
}
class Monkey {
private String name;
public void clime() {
System.out.println("猴子会爬树...");
}
public Monkey(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
//小结,当子类继承了父类,就自动拥有了父类的功能
// 如果子类需要扩展功能,可以通过实现接口的方式扩展
// 可以理解 实现接口 是 对java单继承机制的一种补充
class littleMonkey extends Monkey implements fishAble, birdAble {
public littleMonkey(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(getName() + "通过学习,会游泳");
}
@Override
public void fly() {
System.out.println(getName() + "通过学习,会飞翔");
}
}
输出结果为:
猴子会爬树...
悟空通过学习,会飞翔
悟空通过学习,会游泳
小结:
1、接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计好各种规范(方法),让其它类去实现这些方法。即更加灵活。
2、接口比继承更加灵活
继承是满足 is - a的关系,而接口只需满足 like - a的关系
3、接口在一定程序上实现代码解耦[即:接口规范性+动态绑定机制]
9.7.8、接口多态特性(P410)
-
接口的多态特性
1、多态参数
在前面的Usb接口案例,UsbInterface usbInterface ,既可以接收手机对象,又可以接收相机对象,就体现了 接口 多态(接口引用可以指向实现了接口的类的对象)
package com.hspedu.interface_;
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口多态的体现
//接口类型的变量 a1 可以指向 实现了接口A1的类的对象实例
A1 a1 = new N1();
a1 = new N2();
//继承多态的体现
//父类类型变量q,可以指向继承了Q的子类的实例对象
Q q = new Q1();
q = new Q2();
}
}
// 1
interface A1 {
}
class N1 implements A1 {
}
class N2 implements A1 {
}
//2
class Q {
}
class Q1 extends Q {
}
class Q2 extends Q {
}
2、多态数组
演示一个案例:给Usb数组中,存放Phone 和相机对象,Phone类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call
package com.hspedu.interface_;
public class InterfacePolyArr {
//演示一个案例:
// 给Usb数组中,存放Phone 和相机对象,Phone类还有一个特有的方法call(),
// 请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call
public static void main(String[] args) {
Usb[] usb = new Usb[2];
usb[0] = new Phone_();
usb[1] = new Camera_();
for (int i = 0; i < usb.length; i++) {
usb[i].work();//动态绑定
if (usb[i] instanceof Phone_) {//判断类似是否为Phone类型
((Phone_) usb[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("相机正在工作中");
}
}
输出结果为:
手机正在工作中
手机正在打电话//
相机正在工作中
9.7.9、接口多态传递(继承)(P411)
(接上一节)3、接口存在传递现象
package com.hspedu.interface_;
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new T();
//这就是多态传递现象
IH ih = new T();//IG接口继承了IH,所以ih也可以指向实现IG的类
}
}
interface IH {
void hi();
}
interface IG extends IH {
}
class T implements IG {
@Override
public void hi() {
}
}
9.7.10、接口课堂练习(P412)
看下面代码有没有错误,有错误就修改,改好后,输出什么。
package com.hspedu.interface_;
public class InterfaceExercise02 {
public static void main(String[] args) {
C_ c_ = new C_();
c_.Px();
}
}
interface A {
int x = 0;
}
class B_ {
int x = 1;
}
class C_ extends B_ implements A {
public void Px() {
System.out.println(x);
}
}
修改后:
package com.hspedu.interface_;
public class InterfaceExercise02 {
public static void main(String[] args) {
C_ c_ = new C_();
c_.Px();
}
}
interface A {
int x = 0;
}
class B_ {
int x = 1;
}
class C_ extends B_ implements A {
public void Px() {
System.out.println("接口A的x:" + A.x + "\nB类中的x:" + super.x);
}
}
输出结果为:
接口A的x:0
B类中的x:1
(5)内部类
9.8、内部类(P413~423)
9.8.1、四种内部类(P413)
-
基本介绍
一个类的内部又完整的嵌套类另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员[类的五大成员为:属性、方法、构造器、代码块、内部类],内部类的最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。
-
基本语法
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其他类
}
-
快速入门
package com.hspedu.innerclass;
public class InnerClass01 {//外部其他类
public static void main(String[] args) {
}
}
class Outer {//外部类
private int num = 999;//属性
public Outer(int num) {//构造器
this.num = num;
}
public void m1() {//方法
System.out.println("m1()");
}
{//代码块
System.out.println("代码块");
}
class InnerClass {//内部类
}
}
-
内部类的分类
1、定义在外部类局部位置上(比如方法内):
①局部内部类(有类名)
②匿名内部类(没有类名,重点!!!)
2、定义在外部类的成员位置上:
①成员内部类(没有static修饰)
②静态内部类(使用static修饰)
9.8.2、局部内部类_1(P414)
-
局部内部类的使用
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
}
}
class Outer02 {
private int n1 = 100;
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
class Inner02 {//局部内部类
}
}
}
1、可以直接访问外部类的所有成员,包含私有的
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2(){};
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
class Inner02 {//局部内部类
//1、可以直接访问外部类的所有成员,包含私有的
public void f1(){
System.out.println("n1="+n1);//访问外部类私有属性
m2();//访问外部类私有方法
}
}
}
}
2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2(){};
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。
// 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
final class Inner02 {//局部内部类
//1、可以直接访问外部类的所有成员,包含私有的
public void f1(){
System.out.println("n1="+n1);//访问外部类私有属性
m2();//访问外部类私有方法
}
}
//class NN extends Inner02{}//继承的父类添加了final,所以这里会报错
}
}
3、作用域:仅仅在定义它的方法或代码块中。
4、局部内部类---访问----->外部类的成员[访问方式:直接访问]
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2(){};
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。
// 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
final class Inner02 {//局部内部类(本质仍然是一个类)
//1、可以直接访问外部类的所有成员,包含私有的
public void f1(){
//4、局部内部类---访问----->外部类的成员[访问方式:直接访问],如下所示
System.out.println("n1="+n1);//访问外部类私有属性
m2();//访问外部类私有方法
}
}
//class NN extends Inner02{}//继承的父类添加了final,所以这里会报错
}
}
5、外部类---访问----->局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内)
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2(){};
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。
// 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
final class Inner02 {//局部内部类(本质仍然是一个类)
//1、可以直接访问外部类的所有成员,包含私有的
public void f1(){
//4、局部内部类---访问----->外部类的成员[访问方式:直接访问],如下所示
System.out.println("n1="+n1);//访问外部类私有属性
m2();//访问外部类私有方法
}
}
//class NN extends Inner02{}//如果继承的父类添加了final,所以这里会报错
//5、外部类---访问----->局部内部类的成员
//访问方式:创建对象,再访问(注意:必须在作用域内)
//也就是说,在外部类方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
inner02.f1();
}
}
记住:
1、局部内部类定义在方法/代码块中
2、作用域在方法体或者代码块中
3、本质仍然是一个类
package com.hspedu.innerclass;
public class InnerClass02 {
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() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。
// 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
final class Inner02 {//局部内部类(本质仍然是一个类)
//1、可以直接访问外部类的所有成员,包含私有的
public void f1() {
//4、局部内部类---访问----->外部类的成员[访问方式:直接访问],如下所示
System.out.println("n1=" + n1);//访问外部类私有属性
m2();//访问外部类私有方法
}
}
//class NN extends Inner02{}//如果继承的父类添加了final,所以这里会报错
//5、外部类---访问----->局部内部类的成员
//访问方式:创建对象,再访问(注意:必须在作用域内)
//也就是说,在外部类方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
System.out.println("是不是先走这里,再去调用内部类中的f1方法");
inner02.f1();
}
}
输出结果为:
是不是先走这里,再去调用内部类中的f1方法
n1=100
Outer02的m2方法被调用
9.8.3、局部内部类_2(P415)
6、外部其他类---不能访问----->局部内部类(因为 局部内部类地位是一个局部变量)
7、如果外部类和局部内部类的成员重名时,默认遵循就近原则。如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问。
package com.hspedu.innerclass;
public class InnerClass02 {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.m1();
//7.2、Outer02.this本质就是外部类的对象,即哪个对象调用了m1,Outer02.this就是哪个对象
System.out.println("outer02的hashCode:" + outer02.hashCode());
}
}
class Outer02 {//外部类
private int n1 = 100;
private void m2() {
System.out.println("Outer02的m2方法被调用");
}
public void m1() {//方法
//说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//2、内部类不能添加访问修饰符,因为它的地位就是一个局部变量。
// 局部变量是不能使用修饰符的。但是可以使用final修饰,因为局部变量也可以使用final
final class Inner02 {//局部内部类(本质仍然是一个类)
private int n1 = 999;
//1、可以直接访问外部类的所有成员,包含私有的
public void f1() {
//4、局部内部类---访问----->外部类的成员[访问方式:直接访问],如下所示
System.out.println("局部内部类n1=" + n1);//访问外部类私有属性
m2();//访问外部类私有方法
//7.1、如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问。
System.out.println("外部类n1=" + Outer02.this.n1);
//7.2、Outer02.this本质就是外部类的对象,即哪个对象调用了m1,Outer02.this就是哪个对象
System.out.println("Outer02.this的hashCode:" + Outer02.this.hashCode());
}
}
//class NN extends Inner02{}//如果继承的父类添加了final,所以这里会报错
//5、外部类---访问----->局部内部类的成员
//访问方式:创建对象,再访问(注意:必须在作用域内)
//也就是说,在外部类方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner02 = new Inner02();
System.out.println("是不是先走这里,再去调用内部类中的f1方法");
inner02.f1();
}
}
输出结果为:
是不是先走这里,再去调用内部类中的f1方法
局部内部类n1=999
Outer02的m2方法被调用
外部类n1=100
Outer02.this的hashCode:460141958
outer02的hashCode:460141958
9.8.4、匿名内部类本质(P416)
-
匿名内部类的使用(重要!!!)
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名(系统会自定义类名,只是看不到)。本质上还是一个类(内部类),同时还是一个对象。
1、匿名内部类的基本语法
new 类或接口(参数列表){
类体
};
代码演示:
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
T t = new T();
t.tiger();
}
}
interface IA {
void cry();
}
class T {
public void tiger() {
//创建匿名内部类
//在jdk底层创建匿名内部类T$1,后面立即创建了T$1的实例,并吧地址返回给ia
//匿名内部类使用一次,就不能再次使用
IA ia = new IA() {//编译类型为IA,运行类型为匿名内部类T$1
@Override
public void cry() {
System.out.println("老虎在叫唤...");
}
};
ia.cry();
System.out.println("ia的运行类型" + ia.getClass());//看运行结果,最后面就是系统给匿名内部类定义的类名
}
}
输出结果为:
老虎在叫唤...
ia的运行类型class com.hspedu.innerclass.T$1
9.8.5、匿名内部类的使用(P417)
package com.hspedu.innerclass;
public class AnonymousInnerClass {
public static void main(String[] args) {
T t = new T();
t.tiger();
}
}
interface IA {
void cry();
}
class T {
public void tiger() {
//创建匿名内部类
//在jdk底层创建匿名内部类T$1,后面立即创建了T$1的实例,并吧地址返回给ia
//匿名内部类使用一次,就不能再次使用
IA ia = new IA() {//编译类型为IA,运行类型为匿名内部类T$1
@Override
public void cry() {
System.out.println("老虎在叫唤...");
}
};
ia.cry();
System.out.println("ia的运行类型" + ia.getClass());//看运行结果,最后面就是系统给匿名内部类定义的类名
//普通类的匿名内部类,不强制实现普通类中的方法
Father father = new Father("jack") {//参数列表中的jack,传入Father中的构造器
@Override
public void test() {
System.out.println("测试方法");
}
};
System.out.println("father的运行类型:" + father.getClass());
father.test();
//基于抽象类的匿名内部类,强制实现抽象类中的抽象方法
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("动物吃");
}
};
System.out.println("animal的运行类型" + animal.getClass());
animal.eat();
}
}
class Father {
private String name;
public Father(String name) {//构造器
this.name = name;
System.out.println("接收到了" + name);
}
public void test() {//方法
}
}
abstract class Animal {
abstract void eat();
}
输出结果为:
老虎在叫唤...
ia的运行类型class com.hspedu.innerclass.T$1
接收到了jack
father的运行类型:class com.hspedu.innerclass.T$2
测试方法
animal的运行类型class com.hspedu.innerclass.T$3
动物吃
9.8.6、匿名内部类细节(P418)
(接P416)
2、匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征。调用匿名内部类方法有两种:
package com.hspedu.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.f1();
}
}
class Outer05 {
public void f1() {
//创建一个基于类的匿名内部类
//底层还是 class 匿名内部类 extends Person {}
Person person = new Person() {
@Override
public void hi() {
System.out.println("匿名内部类中的hi()");
}
};
person.hi();//第一种调用方法,动态绑定,运行类型是Outer05$1
new Person(){
@Override
public void hi() {
super.hi();
}
}.hi();//第二种调用方法,可以直接调用,匿名内部类本身也是返回对象
new Person(){
@Override
public void ok(String str) {
super.ok(str);
}
}.ok("匿名内部类调用ok方法");
}
}
class Person {
public void hi() {
System.out.println("Person hi()");
}
public void ok(String str) {
System.out.println("Person ok()" + str);
}
}
输出结果为:
匿名内部类中的hi()
Person hi()
Person ok()匿名内部类调用ok方法
3、可以直接访问外部类的所有成员,包含私有的
4、不能添加访问修饰符,因为它的地位就是一个局部变量。
5、作用域:仅仅在定义它的方法或代码块中
6、匿名内部类---访问----->外部类成员[访问方式:直接访问]
7、外部其他类---不能访问----->匿名内部类。[匿名内部类本身就是一个局部变量]
8、如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
9.8.7、匿名内部类实践(P419)
-
匿名内部类的最佳实践
匿名内部类,当作实参直接传递,简洁高效。
package com.hspedu.innerclass;
public class InnerClassExercise01 {
public static void main(String[] args) {
//1、匿名内部类,当作实参直接传递,简洁高效。//适合单次使用
f1(new IA1() {
//直接传参不会影响实例,
@Override
public void show() {
System.out.println("这是一幅名画...");
}
});
//2、传统方法----->类实现IL1-----》编程领域(硬编码)-----》适合多次使用
f1(new Picture());
}
public static void f1(IA1 ia1) {
ia1.show();
}
}
interface IA1 {
void show();
}
//2、传统方法----->类实现IL1-----》编程领域(硬编码)-----》适合多次使用
class Picture implements IA1{
//在类里修改内容,会影响所以Picture实例
@Override
public void show() {
System.out.println("这是一幅名画...");
}
}
输出结果为:
这是一幅名画...
这是一幅名画...
-
匿名内部类课堂练习
1、有一个铃声接口Bell,里面有个ring方法。
2、有一个手机类cellPhone,具有闹钟功能alarmClock,参数是Bell类型
3、测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
4、再传入另一个匿名内部类(对象),打印:小伙伴上课了
package com.hspedu.innerclass;
public class InnerClassExercise02 {
public static void main(String[] args) {
//1、有一个铃声接口Bell,里面有个ring方法。
//2、有一个手机类cellPhone,具有闹钟功能alarmClock,参数是Bell类型
//3、测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了
//4、再传入另一个匿名内部类(对象),打印:小伙伴上课了
//下面重写了ring
//Bell bell = new Bell() {
// @Override
// public void ring() {
// System.out.println("懒猪起床了");
// }
// }
cellPhone.alarmClock(new Bell() {//cellPhone.alarmClock传入的参数(匿名内部类)需要实现接口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 static void alarmClock(Bell bell) {//编译类型是Bell,运行类型是变化的
bell.ring();//动态绑定机制
}
}
输出结果为:
懒猪起床了
小伙伴上课了
9.8.8、成员内部类_1(P420)
-
成员内部类的使用
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。
1、可以直接访问外部类的所有成员,包含私有的。
package com.hspedu.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.call();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
//成员内部类定义在外部类的的成员位置上
class Inner08 {
public void say() {
//1、可以直接访问外部类的所有成员,包含私有的。
System.out.println("n1 = " + n1 + "\nname = " + name);
}
}
public void call(){
Inner08 inner08 = new Inner08();
inner08.say();
}
}
输出结果为:
n1 = 10
name = 张三
2、可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
package com.hspedu.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.call();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
//成员内部类定义在外部类的的成员位置上
//2、可以添加任意访问修饰符(public、protected、默认、private),因为**它的地位就是一个成员**。
private class Inner08 {
public void say() {
//1、可以直接访问外部类的所有成员,包含私有的。
System.out.println("n1 = " + n1 + "\nname = " + name);
}
}
public class Inner081 {
}
protected class Inner082 {
}
public void call() {
Inner08 inner08 = new Inner08();
inner08.say();
}
}
输出结果为:
n1 = 10
name = 张三
9.8.9、成员内部类_2(P421)
3、作用域:和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
4、成员内部类---访问----->外部类(比如:属性)[访问方式:直接访问]
package com.hspedu.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.call();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
private void f1() {
System.out.println("f1()方法被调用...");
}
//成员内部类定义在外部类的的成员位置上
//2、可以添加任意访问修饰符(public、protected、默认、private),因为**它的地位就是一个成员**。
private class Inner08 {
public void say() {
//1、可以直接访问外部类的所有成员,包含私有的。
System.out.println("n1 = " + n1 + "\nname = " + name);
//4、成员内部类---访问----->外部类(比如:属性)[访问方式:直接访问]
f1();
}
}
public class Inner081 {
}
protected class Inner082 {
}
public void call() {
Inner08 inner08 = new Inner08();
inner08.say();
}
}
输出结果为:
n1 = 10
name = 张三
f1()方法被调用...
5、外部类---访问----->内部类 [访问方式:创建对象,再访问] //上面代码call方法调用成员内部类的say方法
6、外部其他类---访问---成员内部类
package com.hspedu.innerclass;
public class MemberInnerClass {
public static void main(String[] args) {
//6、外部其他类---访问---成员内部类
//方式1
//outer08.new Inner08(); 相当于把 new Inner08(); 当作outer08的成员
Outer08 outer08 = new Outer08();
Outer08.Inner08 inner08 = outer08.new Inner08();
inner08.say();
System.out.println("========================");//分割线
//方式2
//在外部类中,编写一个方法,返回Inner08对象
Outer08.Inner08 inner081 = outer08.getInner08();
inner081.say();
}
}
class Outer08 {
private int n1 = 10;
public String name = "张三";
private void f1() {
System.out.println("f1()方法被调用...");
}
//成员内部类定义在外部类的的成员位置上
//2、可以添加任意访问修饰符(public、protected、默认、private),因为**它的地位就是一个成员**。
public class Inner08 {
public void say() {
//1、可以直接访问外部类的所有成员,包含私有的。
System.out.println("n1 = " + n1 + "\nname = " + name);
//4、成员内部类---访问----->外部类(比如:属性)[访问方式:直接访问]
f1();
}
}
public class Inner081 {
}
protected class Inner082 {
}
public Inner08 getInner08() {
return new Inner08();
}
public void call() {
Inner08 inner08 = new Inner08();
inner08.say();
}
}
输出结果为:
n1 = 10
name = 张三
f1()方法被调用...
========================
n1 = 10
name = 张三
f1()方法被调用...
7、如果外部类和内部类的成员重名是,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用 外部类名.this.成员 去访问
9.8.10、静态内部类_1(P422)
-
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
}
}
class Outer10 {
private int n1 = 10;
public String name1 = "Mike";
private static String name2 = "Jack";
//1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
static class Inner10 {
public void Print() {
//System.out.println("name1 = " + name1);//name1不是静态成员,会报错
System.out.println("name2 = " + name2);
}
}
}
2、可以添加啊任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
}
}
class Outer10 {
private int n1 = 10;
public String name1 = "Mike";
private static String name2 = "Jack";
//1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
public static class Inner10 {//2、可以添加啊任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public void Print() {
//System.out.println("name1 = " + name1);//name1不是静态成员,会报错
System.out.println("name2 = " + name2);
}
}
}
3、作用域:同其他的成员,为整个类体。
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.Call();
}
}
class Outer10 {
private int n1 = 10;
public String name1 = "Mike";
private static String name2 = "Jack";
//1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
public static class Inner10 {//2、可以添加啊任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public void Print() {
//System.out.println("name1 = " + name1);//name1不是静态成员,会报错
System.out.println("name2 = " + name2);
}
}
//3、作用域:同其他的成员,为整个类体。
public void Call() {
Inner10 inner10 = new Inner10();
inner10.Print();
}
}
输出结果为:
name2 = Jack
9.8.11、静态内部类_2(P423)
4、静态内部类---访问-----》外部类(比如:静态属性)[访问方式:直接访问所有静态成员]
5、外部类---访问-----》静态内部类[访问方式:创建对象,再访问]
6、外部其他类---访问---静态内部类
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.Call();
//6、外部其他类---访问---静态内部类
//方式一
Outer10.Inner10 inner10 = new Outer10.Inner10();
//由于是静态内部类,可以通过类名直接访问(前提是满足访问权限)
inner10.Print();
//方式二
//①编写一个方法返回一个内部类实例
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.Print();
//②
Outer10.Inner10 inner10_ = outer10.getInner10_();
inner10_.Print();
}
}
class Outer10 {
private int n1 = 10;
public String name1 = "Mike";
private static String name2 = "Jack";
//1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
public static class Inner10 {//2、可以添加啊任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public void Print() {
//System.out.println("name1 = " + name1);//name1不是静态成员,会报错
System.out.println("name2 = " + name2);
}
}
//3、作用域:同其他的成员,为整个类体。
public void Call() {
Inner10 inner10 = new Inner10();
inner10.Print();
}
//6、外部其他类---访问---静态内部类
//方式二
//编写一个方法返回一个内部类实例
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
输出结果为:
name2 = Jack
name2 = Jack
name2 = Jack
name2 = Jack
7、如果外部类和内部类的成员重名是,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用 外部类名.成员 去访问
package com.hspedu.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer10 outer10 = new Outer10();
outer10.Call();
//6、外部其他类---访问---静态内部类
//方式一
Outer10.Inner10 inner10 = new Outer10.Inner10();
//由于是静态内部类,可以通过类名直接访问(前提是满足访问权限)
inner10.Print();
//方式二
//①编写一个方法返回一个内部类实例
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.Print();
//②
Outer10.Inner10 inner10_ = outer10.getInner10_();
inner10_.Print();
//7、如果外部类和内部类的成员重名是,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用 外部类名.成员 去访问
inner10.Visit();
}
}
class Outer10 {
private int n1 = 10;
public String name1 = "Mike";
private static String name2 = "Jack";
//1、可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员。
public static class Inner10 {//2、可以添加啊任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
private static String name2 = "张三";
public void Print() {
//System.out.println("name1 = " + name1);//name1不是静态成员,会报错
System.out.println("name2 = " + name2);
}
//7、如果外部类和内部类的成员重名是,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用 外部类名.成员 去访问
public void Visit() {
System.out.println("静态内部类name2 = " + name2 + "\n外部类name2 = " + Outer10.name2);
}
}
//3、作用域:同其他的成员,为整个类体。
public void Call() {
Inner10 inner10 = new Inner10();
inner10.Print();
}
//6、外部其他类---访问---静态内部类
//方式二
//编写一个方法返回一个内部类实例
public Inner10 getInner10() {
return new Inner10();
}
public static Inner10 getInner10_() {
return new Inner10();
}
}
输出结果为:
name2 = 张三
name2 = 张三
name2 = 张三
name2 = 张三
静态内部类name2 = 张三
外部类name2 = Jack
9.8.12、内部类课堂练习(P424)
下面代码输出结果为
package com.hspedu.innerclass;
public class InnerClassExercise03 {
public InnerClassExercise03() {
Inner_ inner_ = new Inner_();
inner_.a = 10;
Inner_ inner_1 = new Inner_();
System.out.println(inner_1.a);
}
class Inner_ {
public int a = 5;
}
public static void main(String[] args) {
InnerClassExercise03 innerClassExercise03 = new InnerClassExercise03();
Inner_ inner_ = innerClassExercise03.new Inner_();
System.out.println(inner_.a);
}
}
输出结果为:
5
5