第八章、 面向对象编程(高级部分)
8.1 类变量和类方法
8.1.1 类变量引出
- 类变量-提出问题
问:有一群小孩子在玩堆雪人,不时有新的小孩加入,请问如果知道现在共有多少人在玩?编写程序解决。
(1). 思路分析:
① 在main方法中定义一个变量count
② 当一个孩子加入游戏后count++,最后输出count即可
(2). 实现代码ChildGame.java:
package chapter8.classvarialbe;
public class ChildGame {
public static void main(String[] args) {
int count=0;
Child child1 = new Child("小名");
child1.join();
count++;
Child child2 = new Child("小红");
child2.join();
count++;
Child child3 = new Child("小青");
child3.join();
count++;
System.out.println("一共有:"+count+"人加入了游戏");
}
}
class Child{
private String name;
public Child(String name) {
this.name = name;
}
public void join(){
System.out.println(name+"加入了游戏......");
}
}
/*
小名加入了游戏......
小红加入了游戏......
小青加入了游戏......
一共有:3人加入了游戏
*/
(3). 问题分析
① count是一个独立的对象,不好处理
② 以后需要访问count时很麻烦,没有使用到OOP
③ 因此引出类变量/静态变量
8.1.2 类变量快速入门
思考:如果设计一个int count表示总人数,那么在创建一个小孩时,就把count加1,并且count是同一个类所有对象共享的。这个就用类变量/静态变量。修改ChildGame.java
package chapter8.classvarialbe;
public class ChildGame {
public static void main(String[] args) {
int count=0;
Child child1 = new Child("小名");
child1.join();
// count++;
Child child2 = new Child("小红");
child2.join();
// count++;
Child child3 = new Child("小青");
child3.join();
// count++;
System.out.println("一共有:"+Child.count+"人加入了游戏");
}
}
class Child{
private String name;
public static int count;//可以通过类名.属性直接调用
public Child(String name) {
this.name = name;
}
public void join(){
System.out.println(name+"加入了游戏......");
count++;
}
}
/*
小名加入了游戏......
小红加入了游戏......
小青加入了游戏......
一共有:3人加入了游戏
*/
8.1.3 类变量内存布局
- 两种写法:(主要看jdk版本)
(1). 一种static是在堆中
(2). 一种是在方法区的静态区 - 结论:
(1). static变量是同一个类所有对象共享的
(2). static类变量,在类加载的时候就生成了。
8.1.4 什么是类变量
-
类变量的定义
类变量也叫静态变量或静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。 -
如何定义类变量
定义语法:
-
访问修饰符 static 数据类型 变量名;
-
static 访问修饰符 数据类型 变量名;
- 如何访问类变量VisitStatic.java
类名.类变量名或者 对象名.类变量名[静态变量的访问修饰符的访问权限和范围 和普通属性是一样的]
package chapter8.classvarialbe;
public class VisitStatic {
public static void main(String[] args) {
//类名.类变量名
//说明,类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
System.out.println(A.name);
//对象名.类变量名
A a = new A();
System.out.println(a.name);
}
}
class A{
public static String name="hello";
}
/*
hello
hello
*/
8.1.5 类变量使用注意事项和细节
-
什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生一共交多少钱。Student(name,fee) -
类变量和实例变量(普通属性)区别StaticDetail.java
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。 -
加上static称为类变量或静态变量 ,否则称为实例变量/普通变量/非静态变量
-
类变量可以通过 类名.类变量名 或者对象名.类变量名 来访问,但java设计者推荐我们使用 类名.类变量名方式访问。【前提是:满足访问修饰符的访问权限和范围】
-
实例变量不能通过 类名.类变量名 方式访问。
-
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
-
类变量的生命周期是随着类的加载开始,随着类消亡而销毁。
-
静态属性初始化通常指的是在类定义中直接为静态变量赋予初始值的过程。
8.1.6 类方法快速入门
- 类方法基本介绍
类方法也叫做静态方法。
形式如下:
(1). 访问修饰符 static 数据返回类型 方法名(){ }
(2). static 访问修饰符 数据返回类型 方法名(){ } - 类方法的调用
使用方式:类名.类方法名 或者 对象名.类方法名 【前提是:满足访问修饰符的访问权限和范围】 - 类方法应用案例StaticMethod.java(统计学费总和)
package chapter8.classvarialbe;
import java.util.Map;
public class StaticMethod {
public static void main(String[] args) {
Student student1 = new Student("小明", 3000);
Student student2 = new Student("小红", 2500);
System.out.println(Student.getTotalfee());//5500.0
}
}
class Student{
private String name;
public static double totalfee;
public Student(String name,double fee) {
this.name = name;
totalfee+=fee;
}
public static double getTotalfee(){
return totalfee;
}
}
8.1.7 类方法的实际使用常用
-
类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
比如:工具类中的方法utils
Math类、Arrays类、Collections集合类 -
小结
在实际开发中,往往会将一些通用的方法,设计成静态方法,这样就不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等等
class Cal{
public static double sum(double d1,double d2){
return d1+d2;
}
}
8.1.8 类方法使用注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
(1). 类方法中无this的参数
(2). 普通方法中隐含this的参数 - 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
- 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
- 类方法(静态方法)中,只能访问静态变量或静态方法。
- 普通成员方法,即可以访问普通成员(变量、方法),也可以访问静态成员(变量、方法)。
小结:静态方法,只能访问静态的成员。非静态的方法,也可以访问静态成员和非静态成员。(都必须遵守访问权限)StaticMethodDetail.java
package chapter8.classvarialbe;
public class StaticMethodDetail {
public static void main(String[] args) {
}
}
class D{
private int n1;//非静态变量
private static int n2;//静态变量
public void say(){//普通方法
}
public static void hi(){//静态方法
}
public static void test1(){
//4.类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
// this.n2;//报错
}
public static void test2(){
//5.类方法(静态方法)中,只能访问静态变量或静态方法。
// System.out.println(n1);//报错
System.out.println(n2);
// say();//报错
hi();
}
public void test3(){
//6.普通成员方法,即可以访问普通成员(变量、方法),也可以访问静态成员(变量、方法)。
System.out.println(n1);
System.out.println(n2);
say();
hi();
}
}
8.1.9 练习
- 判断代码是否有误,若有误则修改之后输出什么?若无误则输出什么?
package chapter8.classvarialbe;
public class StaticExercise01 {
static int count=9;
public void count(){
System.out.println("count="+(count++));
}
public static void main(String[] args) {
new StaticExercise01().count();
new StaticExercise01().count();
System.out.println(StaticExercise01.count);
}
/*
count=9
count=10
11
*/
}
- 判断代码是否有误,若有误则修改之后输出什么?若无误则输出什么?
package chapter8.classvarialbe;
public class StaticExercise02 {
public static void main(String[] args) {
System.out.println("Number of Total is "+Person.getTotalPerson());//0
Person p=new Person();
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;
}
}
- 判断代码是否有误,若有误则修改之后输出什么?若无误则total输出什么?
package chapter8.classvarialbe;
public class StaticExercise03 {
public static void main(String[] args) {
Person01.setTotalPerson(3);
new Person01();
// System.out.println(Person01.total);//错误,遵守访问修饰符的规则,private私有
//最后total=4
}
}
class Person01{
private int id;
private static int total=0;
public static void setTotalPerson(int total){
// this.total=total;//报错,静态方法不允许使用对象有关的关键字
Person01.total=total;
}
public Person01(){
total++;
id=total;
}
}
8.2 理解main方法语法
8.2.1 深入理解main方法
解释main方法的形式:public static void main(String[] args){}
Main方法是Java虚拟机调用
Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数,案例演示,接收参数
Java执行的程序 参数1 参数2 参数3
案例分析【Hello.java】
public class Hello{
public static void main(String[] args){
for(int i=0;i<args.length;i++){
System.out.println("第"+i+"一个参数="+args[i]);
}
}
}
8.2.2 main方法使用细节和注意事项
特别提示:
在main()方法中,可以直接调用mian方法所在类的静态方法或静态属性。
但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能用过这个对象去访问类中的非静态成员。
package chapter8.main_;
public class Main01 {
public static void main(String[] args) {
// test01();//错误,依然遵守静态方法只能访问静态成员
test02();
}
public void test01(){
System.out.println("test01");
}
public static void test02(){
System.out.println("test02");
}
}
8.2.3 main方法动态传值
如何在IDEA中使用main传递参数
package chapter8.main_;
public class Main02 {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args["+i+"]="+args[i]);
}
}
}
首先代码默认是没有输出的,要去到工具栏=>运行程序=>编辑配置=>程序实参;
然后输出要传入的参数后运行即可输出
8.3 代码块
8.3.1 代码块快速入门
- 基本介绍
代码块又称为初始化块,属于类中的成员【即是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。 - 基本语法
[修饰符]{
代码
};
- 注意:
(1). 修饰符 可选,要写的话,也只能写static
(2). 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
(3). 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
(4). ;号可以写上,也可以省略。
代码块的好处和案例演示
相等与另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
如果多个构造器中都有重复的语句,可以抽到初始化块中,提高代码的重用性。
代码块的快速入门CodeBlock01.java
package chapter8.codeblock_;
import java.lang.invoke.VarHandle;
public class CodeBlock01 {
public static void main(String[] args) {
Movice movice = new Movice("泰坦尼克号");
/*
电影屏幕打开...
电影广告....
电影开始播放....
这是一个参数的构造器
*/
}
}
class Movice{
private String name;
private double price;
private String director;
//三个构造器=>重载
//(1) 下面的三个构造器都有相同的语句
//(2) 这样代码看起来比较冗余
//(3) 这时我们可以把相同的语句,放到一个代码块中,即可
//(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
//(5) 代码块调用的顺序优先于构造器..
public Movice(String name) {
// System.out.println("电影屏幕打开...");
// System.out.println("电影广告....");
// System.out.println("电影开始播放....");
System.out.println("这是一个参数的构造器");
this.name = name;
}
public Movice(String name, double price) {
// System.out.println("电影屏幕打开...");
// System.out.println("电影广告....");
// System.out.println("电影开始播放....");
System.out.println("这是二个参数的构造器");
this.name = name;
this.price = price;
}
public Movice(String name, double price, String director) {
// System.out.println("电影屏幕打开...");
// System.out.println("电影广告....");
// System.out.println("电影开始播放....");
System.out.println("这是三个参数的构造器");
this.name = name;
this.price = price;
this.director = director;
}
{
System.out.println("电影屏幕打开...");
System.out.println("电影广告....");
System.out.println("电影开始播放....");
};
}
8.3.2 代码块使用注意事项和使用细节
-
Static代码块也叫静态代码块,作用就是对类初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。
-
类什么时候被加载【CodeBlockDetail01.java】
(1). 创建对象实例时(new)
(2). 创建子类对象实例,父类也会被加载
(3). 使用类的静态成员时(静态属性、静态方法)
(4). 案例演示:A类 extends B类 的静态块
package chapter8.codeblock_;
public class CodeBlockDetail01 {
public static void main(String[] args) {
//类什么时候被加载
//(1). 创建对象实例时(new)
// BB bb = new BB();
//(2). 创建子类对象实例,父类也会被加载,而且父类先被加载,再到子类被加载
// AA aa = new AA();
//(3). 使用类的静态成员时(静态属性、静态方法)
// System.out.println(Cat.n1);
//静态代码块,它随着类的加载而执行,并且只会执行一次
DD dd = new DD();
DD dd1 = new DD();
//只会输出一次:这是类DD的静态代码块
}
}
class DD{
static {
System.out.println("这是类DD的静态代码块");
}
}
class Cat{
public static int n1=999;
static {
System.out.println("这是类Cat的静态代码块");
}
}
class BB {
static {
System.out.println("这是类BB的静态代码块");
}
}
class AA extends BB{
static {
System.out.println("这是类AA的静态代码块....");
}
}
-
普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会被调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。
-
创建一个对象时,在一个类 调用顺序是:(重点、难点)
(1). 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一致,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
(2). 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一致,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
(3). 调用构造方法
新写一个类演示【CodeBlockDetail02.java】
package chapter8.codeblock_;
public class CodeBlockDetail02 {
public static void main(String[] args) {
//(1) 静态属性初始化和静态代码块先执行(同级,按顺序),
//(2) 普通属性初始化和普通代码块后执行(同级,按顺序),
//(3) 最后在执行构造器初始化
A01 a01 = new A01();
}
}
class A01{
//静态属性初始化
private static int n1=getVal();
private int n2=getVal2();
//静态代码块
{
System.out.println("A01的普通代码块....");
}
static {
System.out.println("A01的静态代码块...");
}
public A01(){
super();
System.out.println("A01的构造器");
}
public static int getVal(){
System.out.println("getVal() 静态方法被执行");
return 10;
}
public int getVal2(){
System.out.println("getVal2() 普通方法被执行");
return 30;
}
}
/**
* 输出如下:
* getVal() 静态方法被执行
* A01的静态代码块...
* getVal2() 普通方法被执行
* A01的普通代码块....
* A01的构造器
*/
- 构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,新写一个类演示【CodeBlockDetail03.java】,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于 构造器和普通代码块执行的。
package chapter8.codeblock_;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();
}
}
class AAA{
{
System.out.println("AAA 普通代码块");
}
public AAA() {
//隐含了super()
//隐含了调用本类的普通代码块和普通属性初始
System.out.println("AAA() 构造器被调用....");
}
}
class BBB extends AAA{
{
System.out.println("BBB 普通代码块");
}
public BBB() {
//隐含了super()
//隐含了调用本类的普通代码块和普通属性初始
System.out.println("BBB() 构造器被调用....");
}
}
/*
AAA 普通代码块
AAA() 构造器被调用....
BBB 普通代码块
BBB() 构造器被调用....
*/
- 可以看一下创建一个子类对象时(继承关系),它们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下://面试题
(1). 父类的静态代码块和静态属性一致(优先级一致,按定义顺序执行)
(2). 子类的静态代码块和静态属性一致(优先级一致,按定义顺序执行)
(3). 父类的普通代码块和普通属性初始化一致(优先级一致,按定义顺序执行)
(4). 父类的构造方法
(5). 子类的普通代码块和普通属性初始化一致(优先级一致,按定义顺序执行)
(6). 子类的构造方法
C、D类演示【CodeBlockDetail04.java】
package chapter8.codeblock_;
public class CodeBlockDetail04 {
public static void main(String[] args) {
new D();//思考输出什么?
}
}
class C{
private static int n1=getN1();
private int n2=getN2();
static {
System.out.println("C类(父类) 静态代码块的调用.....");//(2)
}
{
System.out.println("C类(父类) 普通代码块的调用.....");//(6)
}
public C() {
System.out.println("C类(父类) 构造器的调用.....");//(7)
}
public static int getN1(){
System.out.println("C类(父类) getN1() 静态方法的调用.....");//(1)
return 10;
}
public int getN2(){
System.out.println("C类(父类) getN2() 普通方法的调用.....");//(5)
return 10;
}
}
class D extends C{
private static int n1=getN01();
private int n2=getN02();
static {
System.out.println("D类(子类) 静态代码块的调用.....");//(4)
}
{
System.out.println("D类(子类) 普通代码块的调用.....");//(9)
}
public D() {
System.out.println("D类(子类) 构造器的调用.....");//(10)
}
public static int getN01(){
System.out.println("D类(子类) getN01() 静态方法的调用.....");//(3)
return 10;
}
public int getN02(){
System.out.println("D类(子类) getN02() 普通方法的调用.....");//(8)
return 10;
}
}
/*
总结:
(1) 父类的静态属性初始化和父类的静态代码块(同级别,按顺序)
(2) 子类的静态属性初始化和子类的静态代码块(同级别,按顺序)
(3) 父类的普通属性初始化和父类的普通代码块(同级别,按顺序)
(4) 父类的构造器
(5) 子类的普通属性初始化和子类的普通代码块(同级别,按顺序)
(6) 子类的构造器
需要注意,静态属性和静态代码块的初始化是在类加载时完成的,与类的实例化无关。
*/
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
8.3.3 练习
- 看看下面代码输出什么?
package chapter8.codeblock_;
public class CodeBlockExercise01 {
public static void main(String[] args) {
System.out.println("total="+Person.total);
System.out.println("total="+Person.total);
}
}
class Person{
public static int total;
static{//只会执行一次
total=100;
System.out.println("这是个静态代码块");
}
}
/*
输出
这是个静态代码块
total=100
total=100
*/
- 看看下面代码输出什么?
package chapter8.codeblock_;
public class CodeBlockExercise02 {
public static void main(String[] args) {
new Test();
}
}
class Sample{
Sample(String s){
System.out.println(s);
}
Sample(){
System.out.println("Sample无参构造器被调用");
}
}
class Test{
Sample sam1=new Sample("sam1成员初始化");//3
static Sample sam2=new Sample("静态成员sam2初始化");//1
static {
System.out.println("静态代码块被执行");//2
if(sam2==null){
System.out.println("sam2 is null");
}
}
Test(){
System.out.println("Test无参构造器被调用");//4
}
}
/*
静态成员sam2初始化
静态代码块被执行
sam1成员初始化
Test无参构造器被调用
*/
8.4 单例设计模式
8.4.1 单例模式饿汉式和懒汉式
-
什么是设计模式
设计模式是软件工程中的一种模板或解决方案,用于解决在软件设计过程中遇到的常见问题。它们是经过验证的、可重用的解决方案,可以帮助开发者避免重复发明轮子,同时提高代码的可读性、可维护性和灵活性。 -
什么是单例设计模式
单例设计模式是一种创建型设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式有两种开发方式:饿汉式和懒汉式 -
单例模式应用实例
演示饿汉式和懒汉式单例模式的实现。步骤如下:
(1). 构造器私有化=>防治直接new
(2). 类的内部创建对象(该对象是static)
(3). 向外暴露一个静态的公共方法。getInstance
(4). 代码实现
package chapter8.single_instance;
public class InstanceSingle {
public static void main(String[] args) {
// //Single01 构造器被调用
// //chapter8.single_instance.Single01@1e643faf
// Single01 instance=Single01.getInstance();
// System.out.println(instance);
//Single01 构造器被调用
//10
System.out.println(Single01.n1);
}
}
//单例模式-饿汉式
class Single01{
public static int n1=10;
//第一步将构造器私有化,防止直接new,不允许有多个对象
private Single01() {System.out.println("Single01 构造器被调用");}
//第二步在类中实例化,只允许有一个对象,为了静态方法能够使用这个对象,只能static
//对象,通常是重量级的对象,饿汉式可能会造成创建了对象,但是没有使用,造成资源浪费。
private static Single01 instance=new Single01();
//提供一个public的静态方法,返回instance,如果不是静态的外部也用不了
public static Single01 getInstance(){
return instance;
}
}
/*
Single01 构造器被调用
10
*/
package chapter8.single_instance;
import java.sql.SQLOutput;
import java.lang.Runtime;
public class InstanceSingle2 {
public static void main(String[] args) {
// Single02 single02=Single02.getInstance();
// System.out.println(single02);
System.out.println(Single02.n2);//10
}
}
//单例模式-懒汉式
class Single02{
public static int n2=10;
//第一步将构造器私有化,防止直接new
private Single02() {
System.out.println("Single02 构造器被调用");
}
//第二步在类中实例化
private static Single02 instance;
//提供一个public的静态方法,返回instance
public static Single02 getInstance(){
if(instance==null){
instance=new Single02();
}
return instance;
}
}
-
总结:
饿汉式:
定义:其名称“饿汉”意味着它在类加载时就立即创建实例,就像一个饿了的人立即寻找食物一样,不会等待。
问题:但是可能会存在创建了对象,但对象没有被使用,就会造成了资源浪费。为什么会存在这个情况呢?假如在饿汉式这个类有个static属性,与饿汉式的构造无关,在外部调用这个属性时就会创建对象,且对象没有使用。这个问题就有懒汉式解决。
懒汉式:
定义:之所以被称为“懒汉”,是因为它的实现方式只在第一次请求单例实例时才创建实例。这种实现方式类似于懒汉的行为,即“需要时才去做”,而不是提前准备好。
问题:只有当用户使用它提供的public static的方法时,才会返回对象,因为是对象是static,无论调用几次方法返回的对象是第一次创建的对象。存在线程安全问题。 -
区别
(1). 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建的。
(2). 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
(3). 饿汉式存在资源浪费的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
(4). 在JavaSE标准类中,java.lang.Runtime就是经典的单例模式。
8.5 final关键字
- 基本介绍:
final 在中文中的意思是“最后的”或“最终的”,在Java编程语言中,final 关键字用于表示一个实体在初始化之后不能被再次修改或覆盖。 - 使用场景:
(1). 修饰类:当使用 final 修饰一个类时,意味着这个类不能被其他类继承。这通常用于工具类或者那些不应该被扩展的类。
(2). 修饰方法:将方法声明为 final 可以防止该方法被子类覆盖。这通常用于不希望改变方法行为的情况。
(3). 修饰属性:对于类的属性(成员变量),使用 final 修饰意味着一旦该属性被初始化后,它的值就不能被改变。这样的属性称为常量。
(4). 修饰局部变量:在方法内部,使用 final 修饰局部变量意味着这个变量一旦被赋值后就不能被重新赋值,但可以被方法内部的代码读取。 - 注意事项:
(1). 对于基本数据类型的 final 变量,其值确实不能被改变。
(2). 对于对象引用类型的 final 变量,变量的引用地址不能被改变,指向的对象内容是可以被修改的,除非对象本身也是不可变的。 - 示例:
(1). 修饰类:
public final class UtilityClass {
// ...
}
(2). 修饰方法:
public class MyClass {
public final void myMethod() {
// ...
}
}
(3). 修饰属性:
public class MyClass {
private final int myConstant = 10;
}
(4). 修饰局部变量:
public void myMethod() {
final int myConstant = 10;
// myConstant = 20; // 这将导致编译错误
}
使用 final 关键字可以提高代码的可读性和安全性,同时在某些情况下,如使用基本数据类型时,还可以提高性能,因为编译器可以对 final 变量进行优化。
- final使用注意事项和细节
(1). final修饰的属性又叫常量,一般用XX_XX_XX来命名
(2). final修饰的属性在定义时,必须赋初值,并且以后都不能再修改,赋值可以在如下位置之一。
① 定义时:如public final double TAX_RATE=0.8;
② 在构造器中
③ 在代码块中
(3). 如果fianl修饰的属性是静态的,则初始化的位置只能是:
① 定义时
② 在静态代码块中,不能在构造器中赋值
(4). final类不能继承,但是可以实例化对象
package chapter8.final_;
public class FinalDetail01 {
}
class A{
/*
(2).final修饰的属性在定义时,必须赋初值,并且以后都不能再修改,赋值可以在如下位置之一。
① 定义时:如public final double TAX_RATE=0.8;
② 在构造器中
③ 在代码块中
(3).如果fianl修饰的属性是静态的,则初始化的位置只能是:
① 定义时
② 在静态代码块中,不能在构造器中赋值
*/
public final double MAX1=10;
public final double MAX2;
public final double MAX3;
public static final double MAX4;
public A() {
MAX2=10;
}
{
MAX3=10;
}
static {
MAX4=10;
}
}
(5). 一般来说,如果一个类已经是fianl类了,就没有必要再讲方法修饰成final方法。
(6). final不能修饰构造方法 (构造器)。
(7). final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。
package chapter8.final_;
public class FinalDetail {
public static void main(String[] args) {
System.out.println(B.num);//1000
}
}
class B{
public final static int num=1000;
static{
System.out.println("B 静态代码块被加载");
}
}
(8). 包装类(Integer、Double、Float、Boolean等都是fianl),Sting也是fianl类
- 练习
(1). 请编写一个程序【FianlExercise01.java】能够计算圆形面积。要求圆周率为3.14.赋值的位置3个方式都写一下。
package chapter8.final_;
public class FianlExercise01 {
public static void main(String[] args) {
new Circle(3).cal(3);
}
}
class Circle{
private double radius;
private final double PI01=3.141592;
private double PI02;
private final double PI03;
public void cal(double radius){
System.out.println("这是定义时赋值的PI01=:"+PI01+"\t得出的面积:"+PI01*radius*radius);
}
public Circle(double radius){
PI02=3.14159;
System.out.println("这是构造器赋值的PI02="+PI02+"\t得出的面积:"+PI02*radius*radius);;
}
{
PI03=3.1415;
System.out.println("这是代码块赋值的PI03="+PI03+"\t得出面积:"+PI03*radius*radius);
}
}
/*
这是代码块赋值的PI03=3.1415 得出面积:0.0
这是构造器赋值的PI02=3.14159 得出的面积:28.274309999999996
这是定义时赋值的PI01=:3.141592 得出的面积:28.274328000000004
*/
(2). 判断下面代码哪里有错误?
class Some{
private int add(final int x){
++x;//错误
return x+1;
}
}
8.6 抽象类
8.6.1 抽象类的引出
由下面代码可知:当父类的某些方法,需要声明,但是又不知道如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类。
class Animal{
private String name;
public Animal(String name) {
this.name = name;
}
//这里的eat方法,实现了但无意义
//即:父类方法不确定性的问题
//===>考虑讲该方法设计为抽象(abstract)方法
//===>所谓抽象方法就是没有实现的方法,即没有方法体
//===>当一个类中存在抽象方法时,需要将该类声明为abstract类
public void eat(){
System.out.println("这是一个动物,但是不知道该吃什么样的食物");
}
}
解决方法-用关键字abstract将该类设计为抽象类,再将该方法设计为抽象方法,如下:
abstract class Animal{
String name;
int age;
abstract public void eat();
}
8.6.2 抽象类的细节
- 抽象类的介绍
(1). 用abstract关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{
}
(2). 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
(3). 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
(4). 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多 - 抽象类使用的注意事项和细节讨论
(1). 抽象类不能被实例化。
(2). 抽象类不一定都要包含abstract方法。也就是,抽象类中可以没有abstract方法
(3). 一旦类包含了abstract方法,则这个类必须声明为abstract
(4). Abstract只能修饰类和方法,不能修饰属性和其他的。
(5). 抽象类可以有任意成员【因为抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
(6). 抽象方法不能有主体,即不能实现如下:
(7). 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract。所谓实现就是加了个方法体
(8). 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写违背的。子类能够重写的方法有以下限制:
① 访问级别:子类只能重写那些在父类中不是 private 的方法。如果一个方法是 private 的,它不能被子类重写,因为私有方法在类外部是不可见的。
② 方法签名:重写的方法必须具有与被重写方法相同的方法签名(即相同的方法名、参数列表和返回类型)。
③ 可见性:如果父类的方法声明为 protected 或没有指定修饰符(默认访问级别),那么子类可以重写这个方法。
④ 注解:如果父类的方法用 final 修饰,那么这个方法不能被重写。
⑤ 静态方法:静态方法可以在子类中隐藏(hide),但不能被重写。子类可以提供一个具有相同名字和参数的静态方法,但这不会覆盖父类中的静态方法。
8.6.3 练习
-
思考:abstract final class A{} 能编译通过吗,为什么?
不能。abstract 和 final 关键字在类定义中具有相反的含义:
abstract 类表示这个类是抽象的,不能被实例化,并且可能包含抽象方法(没有实现的方法)。
final 类表示这个类不能被继承。 -
思考:abstract public static void test1(); 能编译通过吗,为什么?
不能。abstract 关键字用于声明抽象方法。抽象方法不能是 static 的,因为静态方法不属于类的实例,而是属于类本身,而抽象方法需要被子类实例化后重写实现。 -
思考:abstract private void test2(); 能编译通过吗,为什么?
不能。抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写违背的。 -
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。
提供必要的构造器和抽象方法:work()。对于Manager类来说,它计算员工,还具有奖金(bonus)属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示“经理/普通员工 名字 工作中…”
- Employee类
package chapter8.abstract_;
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public abstract String work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
CommonEmployee类
package chapter8.abstract_;
public class CommonEmployee extends Employee{
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public String work() {
return "普通员工 "+getName()+" 正在工作中......";
}
}
Manager类
package chapter8.abstract_;
public class Manager extends Employee {
private double bouns;
public Manager(String name, int id, double salary, double bouns) {
super(name, id, salary);
this.bouns = bouns;
}
public String work(){
return "经理 "+getName()+" 正在工作中...";
}
public double getBouns() {
return bouns;
}
public void setBouns(double bouns) {
this.bouns = bouns;
}
}
测试类AbstractExercise01
package chapter8.abstract_;
public class AbstractExercise01 {
public static void main(String[] args) {
Manager mark = new Manager("mark", 112, 8000, 20000);
System.out.println(mark.work());
CommonEmployee smith = new CommonEmployee("smith", 115, 10000);
System.out.println(smith.work());
}
}
/*
经理 mark 正在工作中...
普通员工 smith 正在工作中......
*/
8.6.4 抽象类最佳实践-模版设计模式
- 最佳实践
(1). 需求
① 有多个类,完成不同的任务job
② 要求统计各自完成任务的时间
③ 请编程实现 TestTemplate.java
需要使用的时间方法
正常写法的思路:
类Calulate
package chapter8.abstract_;
public class Calulate {
public void calulateTime(){
//得到当前时间
long start=System.currentTimeMillis();
job();
//得到结束时间
long end=System.currentTimeMillis();
System.out.println("Calulate执行时间:"+(end-start)+"毫秒");
}
//计算任务:1+....100000
public void job(){
int num=0;
for (int i = 0; i <= 100000; i++) {
num+=i;
}
}
}
类Calulate02
package chapter8.abstract_;
public class Calulate02 {
public void calulateTime(){
//得到当前时间
long start=System.currentTimeMillis();
job();
//得到结束时间
long end=System.currentTimeMillis();
System.out.println("Calulate02执行时间:"+(end-start)+"毫秒");
}
//计算任务:1+....1000000
public void job(){
int num=0;
for (int i = 0; i <= 1000000; i++) {
num+=i;
}
}
}
package chapter8.abstract_;
public class TestTemplate {
public static void main(String[] args) {
new Calulate().calulateTime();
new Calulate02().calulateTime();
}
}
/*
Calulate执行时间:1毫秒
Calulate02执行时间:2毫秒
*/
发现问题:在类Calulate和类Calulate02中的calulateTime方法有着大量重复的代码。
模版设计:建立一个父类,写一个抽象方法job(),一个普通方法calulateTime(),在普通方法中放入job调用。到子类继承重写即可。
模版父类JobTemplate
package chapter8.abstract_;
public abstract class JobTemplate {
// 抽象方法,由子类实现具体的任务
public abstract void job();
// 普通方法,用于计算任务执行时间
public void calculateTime() {
// 得到当前时间
long start = System.currentTimeMillis();
job(); // 调用抽象方法,执行具体的任务,遵循动态绑定机制
// 得到结束时间
long end = System.currentTimeMillis();
System.out.println(this.getClass().getSimpleName() + "执行时间:" + (end - start) + "毫秒");
}
}
子类SubJob
package chapter8.abstract_;
public class SubJob extends JobTemplate{
@Override
public void job() {
int num=0;
for (int i = 0; i <= 100000; i++) {
num+=i;
}
}
}
子类SubJob2
package chapter8.abstract_;
public class SubJob2 extends JobTemplate{
@Override
public void job() {
int num = 0;
for (int i = 0; i <= 1000000; i++) {
num += i;
}
}
}
最终运行程序类TestTemplate
package chapter8.abstract_;
public class TestTemplate {
public static void main(String[] args) {
new SubJob().calculateTime();
new SubJob2().calculateTime();
}
}
/*
SubJob执行时间:1毫秒
SubJob2执行时间:1毫秒
*/
8.7 接口
8.7.1 接口快速入门
现实中usb插槽就是典型的接口。USB 接口定义了各种规定,但它并不关心连接到端口上的是哪种设备,可以把手机,相机,up都插在usb插槽上。
这样的设计需求在Java编程中也是会大量存在的。模拟上述文字代码:
接口Usb
package chapter8.interface_;
public interface Usb {
void start();
void stop();
}
类Phone实现接口Usb
package chapter8.interface_;
import java.sql.SQLOutput;
public class Phone implements Usb{//实现接口,就是把接口中的方法实现
@Override
public void start() {
System.out.println("手机 开始工作了......");
}
@Override
public void stop() {
System.out.println("手机 停止工作了......");
}
}
类Camera实现接口Usb
package chapter8.interface_;
public class Camera implements Usb{
@Override
public void start() {
System.out.println("相机 开始工作了......");
}
@Override
public void stop() {
System.out.println("相机 停止工作了......");
}
}
通过Computer类可以接收任何Usb接口进行工作
package chapter8.interface_;
public class Computer {
public void working(Usb usb){
usb.start();
usb.stop();
}
}
最终运行程序类Interface01
package chapter8.interface_;
public class Interface01 {
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
computer.working(phone);
computer.working(camera);
}
}
/*
手机 开始工作了......
手机 停止工作了......
相机 开始工作了......
相机 停止工作了......
*/
8.7.2 接口的基本介绍
- 基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
interface 接口名{
//属性
//方法
}
class 类名 implement 接口{
自己属性;
自己方法;
必须实现接口的抽象方法
}
- 实操
interface Interface{
//写属性,必须初始化
int a=10;
//写方法,抽象方法、静态方法、默认方法,其中抽象方法可以省略 abstract 关键字
public void a1();
default void a2(){ }
public static void a3(){ }
}
class A implements Interface{//A implements实现 接口 必须实现抽象方法
@Override
public void a1() {
//实现抽象方法
}
@Override
public void a2() {
//重写方法
}
}
- 小结:
(1). 在jdk7.0前 接口里的所有方法都没有方法体,即都是抽象方法。
(2). 在jdk8.0后,接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
8.7.3 接口应用场景
接口在Java编程中扮演着非常重要的角色,它们提供了一种机制来定义方法的规范,而不需要关心这些方法的具体实现。以下是两个接口应用场景的例子:
- 定义标准行为
场景描述:假设你正在开发一个图形编辑软件,需要支持多种形状,如圆形、矩形、三角形等。每种形状都有绘制(draw)和计算面积(area)的基本行为。如果每个图形各自绘制图形和计算面积都有自己的方法,那在调用起来是很麻烦的。所以使用接口定义抽象方法,让具体的图像来实现。
接口:
package chapter8.interface_;
public interface Shape {
public void draw();//定义绘制图形
public double area();//定义计算面积
}
圆形形状实现 Shape 接口:
package chapter8.interface_;
public class Circle implements Shape{
private double radius;
@Override
public void draw() {
System.out.println("该圆形半径为:"+radius);
}
@Override
public double area() {
return Math.PI*radius*radius;
}
}
矩形形状实现 Shape 接口:
package chapter8.interface_;
public class Rectangle implements Shape{
private double width;
private double height;
@Override
public void draw() {
System.out.println("该矩形的长:"+width+"高:"+height);
}
@Override
public double area() {
return width*height;
}
}
8.7.4 注意事项和细节
(1). 接口不能被实例化
(2). 接口中所有的方法是public方法,接口中抽象方法,可以省略public abstract关键字修饰:
(3). 一个普通类实现接口,就必须将该接口的所有方法都实现。可以使用快捷键:将光标放到接口上,alt+enter即可快速完成。
(4). 抽象类实现接口,可以不用实现接口的方法
(5). 一个类同时可以实现多个接口
(6). 接口中的属性,只能是final的,而且是public static final修饰符。比如int a=1;实际上是public static final int a=1;(必须初始化)
(7). 接口中属性的访问形式:接口名.属性名
(8). 一个接口不能继承其他的类,但是可以继承多个别的接口
(9). 接口修饰符只能是public、默认,这一点和类的修饰符是一样的。
package chapter8.interface_;
public class InterfaceDetail01 {
public static void main(String[] args) {
System.out.println(Person01.age);//调用接口属性,输出20
}
}
//一个接口不能继承其他的类,但是可以继承多个别的接口
interface Person extends Person01,Person02{}
interface Person01{
int age=20;//等价于public static final int age=20;
void cry();
}
interface Person02{
void hi();
}
//一个类可以实现多个类
class someone implements Person01,Person02{
@Override
public void cry() {
}
@Override
public void hi() {
}
}
8.7.5 实现接口与继承类的区别
可以理解为:实现接口是Java单继承的补充。
假如一个母猴子作为父类,小猴子作为子类。小猴子作为子类继承了父类会爬树的技能。但是小猴子还想飞和游泳等技能,但是Java只支持单继承,不能多继承,也就是不能从鸟和鱼哪里继承技能过来。所以就只能用接口实现啦。
package chapter8.interface_;
public class ExtendsVsInterface {
public static void main(String[] args) {
LittleMonkey littleMonkey = new LittleMonkey("悟空");
littleMonkey.climbing();
littleMonkey.flying();
littleMonkey.swimming();
}
}
//小结:
//当子类继承了父类,就自动拥有父类的功能
//如果子类需要扩展功能,可以通过实现接口的方法去扩展
//可以理解为实现接口是Java单继承的补充
interface Fishable{
void swimming();
}
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;
}
}
class LittleMonkey extends Monkey implements Fishable,Birdable{
public LittleMonkey(String name) {
super(name);
}
@Override
public void swimming() {
System.out.println(this.getName()+"学会了飞翔......");
}
@Override
public void flying() {
System.out.println(this.getName()+"学会了游泳.......");
}
}
/*
悟空正在爬树.....
悟空学会了游泳.......
悟空学会了飞翔......
*/
总结:
(1). 接口和继承解决的问题不同
- 继承的价值主要在于:解决代码的复用和可维护性
- 接口的价值主要在于:设计,设计号各种规范(方法),让其他类去实现这些方法。即更加的灵活。
(2). 接口比继承更灵活
- 接口比继承更加灵活,继承是满足is-a的关系,而接口只需满足like-a的关系。
(3). 接口在一定程度生实现代码解耦[即 接口规范性+动态绑定机制]
8.7.6 接口的多态特性
- 多态参数
在前面的Usb接口案例,Usb usb,既可以接收手机对象,又可以接收相机对象,就体现了接口多态(接口引用可以指向实现了接口的类的对象)
package chapter8.interface_;
public class InterfacePolyParameter {
public static void main(String[] args) {
//接口的多态体现
//接口类型的变量 aaa 可以指向 实现AAA接口类的对象实例
AAA aaa = new BBB();
aaa=new CCC();
}
}
interface AAA{}
class BBB implements AAA{}
class CCC implements AAA{}
- 多态数组
给Usb_数组中,存放音频播放器(AudioPlayer)对象和视频播放器(VideoPlayer)对象。音频播放器除了具备媒体播放的基本功能外,还有一个特有的调整音量(adjustVolume)功能。接口存在多态传递现象。遍历Usb_,调用Usb_接口定义的方法。如果是AudioPlayer还需调用它特有的方法adjustVolume。
package chapter8.interface_;
public class InterfacePolyArr {
public static void main(String[] args) {
Usb_[] usb_s=new Usb_[2];
usb_s[0]=new AudioPlayer();//音频
usb_s[1]=new VideoPlayer();//视频
for (int i = 0; i < usb_s.length; i++) {
usb_s[i].player();
if(usb_s[i] instanceof AudioPlayer){ //instanceof判断的是运行类型
// usb_s[i].adjustVolume();//默认是不行,需要向下转型才可调用实现类特有方法
// AudioPlayer audioPlayer=(AudioPlayer) usb_s[i] ;
// audioPlayer.adjustVolume();//可以简写
((AudioPlayer) usb_s[i]).adjustVolume();
}
}
}
}
interface Usb_{
void player();
}
class AudioPlayer implements Usb_{//音频
@Override
public void player() {
System.out.println("音频播放器正在播放音乐....");
}
public void adjustVolume(){
System.out.println("音频播放器正在调节音量......");
}
}
class VideoPlayer implements Usb_{//视频
@Override
public void player() {
System.out.println("视频播放器正在播放音乐....");
}
}
/*
音频播放器正在播放音乐....
音频播放器正在调节音量......
视频播放器正在播放音乐....
*/
- 接口存在多态传递现象
package chapter8.interface_;
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
D d = new E();
//如果D接口继承C接口,而E类实现了D接口,那么也可以实现C接口,且C和D接口的抽象方法都要实现
C c=new E();
}
}
interface C{
void c();
}
interface D extends C{}
class E implements D{
@Override
public void c() {
}
}
8.7.7 练习
- 判断语句是否正确,若不正确请修改,若正确求写出输出?为什么?
package chapter8.interface_;
public class InterfaceExercise01 {
public static void main(String[] args) {
B b = new B();
System.out.println(b.a);//20
System.out.println(AA.a);//20
System.out.println(B.a);//20
}
}
interface AA{
int a=20;
}
class B implements AA{}
- 判断语句是否正确,若不正确请修改,若正确求写出输出?为什么?
package chapter8.interface_;
import static chapter8.interface_.AAAA.x;
public class InterfaceExercise02 {
public static void main(String[] args) {
new CCCC().pX();
}
}
interface AAAA{
int x=0;//等价于public static final int x=0
}
class BBBB{
int x=1;//普通属性
}
class CCCC extends BBBB implements AAAA{
public void pX(){
// System.out.println(x);//错误对 'x' 的引用不明确,'BBBB.x' 和 'AAAA.x' 均匹配
//明确指定x,访问接口的x就使用AAAA.x。访问父类的x就使用super.x
System.out.println(AAAA.x+" "+super.x);
}
}
8.8 内部类(重点)
8.8.1 四种内部类
- 基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【思考:类的五大成员是那些?[属性、方法、构造器、代码块、内部类]】,内部类最大的特点是可以直接访问私有属性,并且可以体现类与类之间的包含关系。 - 基本语法
class Outer{//外部类
class Inner{//内部类
}
}
class Other{//外部其他类
}
- 快速入门案例
package chapter8.innerclass;
public class InnerClass01 {//外部其他类
public static void main(String[] args) {
}
}
class Outer{//外部类
private int n1=10;//属性
public Outer(int n1) {//构造器
this.n1 = n1;
}
public void n1(){//方法
System.out.println("n1()");
}
{//代码块
System.out.println("代码块");
}
class Inner{//内部类
}
}
- 内部类的分类
(1). 定义在外部类局部位置上(比如方法内):
① 局部内部类(有类名)
② 匿名内部类(没有类名,重点!!!!!!!!)
(2). 定义在外部类的成员位置上:
① 成员内部类(没用static修饰)
② 静态内部类(使用static修饰)
8.8.2 局部内部类
局部内部类的基本介绍
(1). 说明:局部内部类是定义在外部类的局部位置,比如方法(或代码块)中,并且有类名。
(2). 可以直接访问外部类的所有成员,包含私有的。
(3). 这个局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用fianl修饰,因为局部变量也可以使用final。
(4). 作用域:仅仅在定义它的方法或代码块中。即外部类的其他成员无法访问,也就是定义在它的方法或代码块中的其他成员可以访问。
(5). 局部内部类—访问---->外部类的成员【访问方式:直接访问】
(6). 外部类-----访问---->局部内部类的成员【访问方式:创建对象,再访问(注意:必须在作用域内)】
(7). 外部其他类—不能访问------>局部内部类(因为局部内部类地位是一个局部变量)
(8). 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
System.out.println(“外部类的n2=”+外部类名.this.n2);
package chapter8.innerclass;
import java.net.SocketTimeoutException;
public class LocalInnerClass {//外部其他类
public static void main(String[] args) {
Outer01 outer01 = new Outer01();
outer01.m1();
}
}
class Outer01{//外部类
private int n1=100;//私有属性
private int n2=200;
private void m2(){//私有方法
System.out.println("Outer01 m2()");
}
public void m1(){//普通方法
//1.局部内部类是定义在外部类的局部位置,比如方法(或代码块)中,并且有类名。
//3.这个局部内部类不能添加访问修饰符,但是可以使用fianl修饰
//4.作用域:仅仅在定义它的方法或代码块中
int n2=800;//和Inner01的n2同名,默认隐藏int n2=800;
class Inner01{//局部内部类(本质上还是一个类)
//2.可以直接访问外部类的所有成员,包含私有的。
int n2=500;
public void f1(){
//5.局部内部类可以直接访问外部类的所有成员,比如下面的:n1和m2()
System.out.println("n1="+n1);
m2();
//7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
//Outer01.this 本质就是外部类的对象,即那个对象调用了m1,Outer01.this就是那个对象
System.out.println("外部类的n2=" +Outer01.this.n2);
System.out.println("内部类的n2="+n2);
}
}
//6.外部类访问局部内部类:在定义局部内部类中的方法->创建对象,然后调用方法即可(注意:必须在作用域内)】
Inner01 inner01=new Inner01();
inner01.f1();
{//代码块
class Inter02{}
}
}
}
/*
n1=100
Outer01 m2()
外部类的n2=200
内部类的n2=500
*/
8.8.3 匿名内部类
匿名内部类的使用(重要!!!!!)
说明:匿名内部类定义在外部类的局部位置,比如方法中,并且没有类名
涉及到:继承、多态、动态绑定、内部类
- 匿名内部类的基本语法:
New 类或接口(){
类体
};
本质是类、内部类、该类没有名字(其实JVM默认给了名字,但是用不了的)
同时还是一个对象
package chapter8.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.需求:向使用接口,并创建对象。传统的写法:写一个类去实现这个接口,并创建对象
//2.但是问题来了,假如这个类只使用一次,后面就不在使用了,假如这个需求非常多,就会造成代码冗余
//3.这时候就可以使用匿名内部类来简化开发
//4.child的编译类型?A
//5.child的运行类型?就是匿名内部类 XXX => Outer04$1
/*
看底层 会分配 类名 Outer04$1
class Outer04$1 implements A{
@Override
public void cry() {
System.out.println("小孩在哭.....");
}
}
可以输出看看:
System.out.println("child"+child.getClass());//childclass chapter8.innerclass.Outer04$1
*/
//7.jdk底层在创建匿名内部类 Outer04$1 ,立即马上就创建了Outer04$1实例,并且把地址返回给child,也就是说child其实是个对象
//8.匿名内部类使用一次,就不能再使用了
A child =new A(){
@Override
public void cry() {
System.out.println("小孩在哭.....");
}
};
child.cry();
System.out.println("child"+child.getClass());//childclass chapter8.innerclass.Outer04$1
//基于类的匿名构造器
//1.father编译类型 Father
//2.father运行类型 Outer04$2
//3.底层会创建匿名内部类
/*
class Outer04$2 entends Father{
@Override
public void test() {
super.test();
}
}
*/
//4.mark会传给Father的构造器,如果Father是抽象类,其中的抽象方法必须实现,普通方法可以重写也可不重写
Father father=new Father("mark"){
@Override
public void test() {
super.test();
System.out.println(2);
}
};
father.test();
}
}
interface A{//接口
void cry();
}
class Father{
public Father(String name) {
}
public void test(){
System.out.println("1");
}
}
/*
小孩在哭.....
childclass chapter8.innerclass.Outer04$1
1
2
*/
-
匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面的代码分析可以看出这个特点,因此可以调用匿名内部类的方法。
-
可以直接方法外部类的所有成员,包含私有的
-
匿名内部类不能添加访问修饰符,因为它的地位就是一个局部变量
-
作用域:仅仅在定义它的方法或代码块中
-
匿名内部类----访问---->外部类成员【访问方式:直接访问】
-
外部其他类------不能访问------>匿名内部类(因为匿名内部类地位就是一个局部变量)
-
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
package chapter8.innerclass;
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer05 outer05 = new Outer05();
outer05.info();
System.out.println("main Outer05 hashcode="+outer05);
}
}
class Outer05{
private int n1=29;
public void info(){
Person p=new Person(){
private int n1=19;
@Override
public void hi() {
//3.可以直接方法外部类的所有成员,包含私有的
//8.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
System.out.println("匿名内部类n1="+n1);
System.out.println("外部类n1="+Outer05.this.n1);
//Outer05.this 就是调用info() 的对象
System.out.println("Outer05.this hashcode="+Outer05.this);
System.out.println("Outer05 hi()");
}
};
p.hi();//动态绑定,运行类型是Outer05$1
//也可以直接调用,因为匿名内部类本身也是返回对象
new Person(){
@Override
public void hi() {
System.out.println("Outer05 hi()~~~~~");
}
}.hi();
}
}
class Person{
public void hi(){
System.out.println("Person hi()~");
}
}
/*
匿名内部类n1=19
外部类n1=29
Outer05.this hashcode=chapter8.innerclass.Outer05@dfd3711
Outer05 hi()
Outer05 hi()~~~~~
main Outer05 hashcode=chapter8.innerclass.Outer05@dfd3711
*/
- 练习
(1). 把匿名内部类当做实参直接传递,简洁高效(这种写法很常见)
package chapter8.innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
//当做实参直接传递,简洁高效
f1(new AA() {
@Override
public void show() {
System.out.println("hi~~");
}
});
/*
理解这个实参传匿名内部类:首先匿名内部类最终返回的是对象。上面代码流程:
1.在底层 自动分配类名实现这个接口AA,然后立即马上实例化这个类InnerClassExercise$1,然后将这个对象返回
InnerClassExercise$1 implements AA{
@Override
public void show() {
System.out.println("hi~~");
}
};
假设这个是实例化:InnerClassExercise$1 ice=new InnerClassExercise$1();
ice就等同于f1()方法里的内部类,那等于ice.show(),这是show()就是上面重写的show(),然后就输出hi~~
这上面写的是具体流程,但是具体的名字是jvm分配的,这里是为了理解才这样设置的
*/
//传统写法:写一个类实现该接口,该new这个实现接口类
}
public static void f1(AA aa){
aa.show();
}
}
interface AA{
void show();
}
(2). 有一个铃声接口Bell,里面有个ring方法。
有一个手机类CellPhone,具有闹钟功能alarmclock,参数是Bell类型
测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床啦~~~
再传入另一个匿名内部类(对象),打印:小伙子上课啦~~~
package chapter8.innerclass;
public class InnerClassExercise02 {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
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.ring();
}
}
/*
懒猪起床啦~~~
小伙伴上课啦~~~
*/
(3). 假设有一个需求,需要创建一个简单的计算器类,可以进行加法和乘法操作,并且可以指定两个操作数。可以使用匿名内部类来实现这个需求。定义一个接口Operation,里面定义一个方法calculate用于返回a,b;定义一个类Calculator里面有个用于执行的方法executeOperation,调用calculate方法。最终在main方法中进行具体的计算操作。
package chapter8.innerclass;
import java.util.Scanner;
public class Calculator {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int a=scanner.nextInt();
int b=scanner.nextInt();
//加法操作
int addresult=executeOperation(a,b,new Operation(){
@Override
public int calculate(int a, int b) {
return a+b;
}
});
System.out.println(addresult);
//乘法同理
}
public static int executeOperation(int a,int b,Operation operation){
return operation.calculate(a,b);
}
}
interface Operation{
int calculate(int a,int b);
}
8.8.4 成员内部类
成员内部类的使用
说明成员内部类是定义在外部类的成员位置,并且没有static修饰。
- 可以直接方法外部类的所有成员,包含私有的
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
package chapter8.innerclass;
public class MemberInnerClass01 {
public static void main(String[] args) {
Outer08 outer08 = new Outer08();
outer08.say();
}
}
class Outer08{
private int n1=10;
public String name="mark";
//1.可以直接方法外部类的所有成员,包含私有的
//2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
private class Inner08{
private int n2=20;
public void hi(){
System.out.println(name+n1);
}
}
public void say(){
Inner08 inner08 = new Inner08();
inner08.hi();
System.out.println(inner08.n2);
}
}
/*
mark10
20
*/
- 作用域:和外部类的其他成员一样,为整个类体,比如前面的案例,在外部类的成员方法中创建成员内部类对象,在调用方法。
- 成员内部类----访问---->外部类成员(比如属性)【访问方式:直接访问】
- 外部类------访问------>成员内部类【访问方式:创建对象,再访问】
- 外部其他类----访问------>成员内部类
package chapter8.innerclass;
public class MemberInnerClass02 {
public static void main(String[] args) {
//外部其他类,使用成员内部类的两种方式
//第一种:outer09.new Inner09();相等域把new Inner09()当做是outer09的成员
Outer09 outer09=new Outer09();
Outer09.Inner09 inner09=outer09.new Inner09();
inner09.hi();
//第二种:在外部类中定义一个方法,该方法返回 Inner09对象
Outer09.Inner09 inner09Instance = outer09.getInner09Instance();
inner09Instance.hi();
}
}
class Outer09{
class Inner09{
public void hi(){
System.out.println("hello~");
}
}
public Inner09 getInner09Instance(){
return new Inner09();
}
}
/*
hello~
hello~
*/
- 如果外部类和成员内部类的成员重名时,成员内部类访问的话,默认遵循就行原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
package chapter8.innerclass;
public class MemberInnerClass03 {
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
Outer03.Inner03 inner03 = outer03.new Inner03();
inner03.pri();
}
}
class Outer03{
private int n1=10;
class Inner03{
private int n1=20;
public void pri(){
System.out.println("Inner03 n1="+n1);
System.out.println("Outer03 n1="+Outer03.this.n1);
}
}
}
/*
Inner03 n1=20
Outer03 n1=10
*/
8.8.5 静态内部类
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且有static修饰。
- 可以直接访问外部类的所有静态成员,包括私有的,但不能直接访问非静态成员。
- 可以添加任意修饰符(public、protected、默认、private),因为它的地位就是一个成员。
- 作用域:同其他的成员,为整个类体。
- 静态内部类----访问----->外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
- 外部类-----访问----->静态内部类 【访问方式:创建对象,再访问】
package chapter8.innerclass;
public class StaticInnerClass01 {
public static void main(String[] args) {
Outer1 outer1 = new Outer1();
outer1.f1();
}
}
class Outer1{
private int n1=20;
private static int n2=19;
private static class Inner1{
public void hi(){
// System.out.println(n1);//报错
System.out.println(n2);
}
}
public void f1(){
Inner1 inner1 = new Inner1();
inner1.hi();
}
}
//输出19
- 外部其他类----访问---->静态内部类
package chapter8.innerclass;
public class StaticInnerClass02 {
public static void main(String[] args) {
//外部其他类 使用静态内部类
//方式1:因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer2.Inner2 inner2=new Outer2.Inner2();
inner2.hi();
//方式2:编写一个方法(可以是静态的,也可以是非静态的),可以返回静态内部类对象的实例
Outer2 outer2 =new Outer2();
Outer2.Inner2 inner21=outer2.getInner21();
inner21.hi();
Outer2.Inner2 inner22=Outer2.getInner22();
inner22.hi();
}
}
class Outer2{
static class Inner2{
public void hi(){
System.out.println("hhhhhh");
}
}
public Inner2 getInner21(){
return new Inner2();
}
public static Inner2 getInner22(){
return new Inner2();
}
}
/*
hhhhhh
hhhhhh
hhhhhh
*/
- 如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问。
package chapter8.innerclass;
public class StaticInnerClass03 {
public static void main(String[] args) {
Outer3.Inner3 inner3 = new Outer3.Inner3();
inner3.hi();
}
}
class Outer3{
private static int n1=10;
static class Inner3{
private static int n1=20;
public void hi(){
System.out.println("Inner3 n1="+n1);
System.out.println("Outer3 n1="+Outer3.n1);
}
}
}
/*
Inner3 n1=20
Outer3 n1=10
*/
- 练习 判断下面代码输出什么?
package chapter8.innerclass;
public class InnerClassExerse {
public static void main(String[] args) {
Test test = new Test();
Test.Inner inner = test.new Inner();
System.out.println(inner.n);//2
}
}
class Test{
public Test() {
Inner inner01 = new Inner();
inner01.n=10;
Inner inner02 = new Inner();
System.out.println(inner02.n);//2
}
class Inner{
public int n=2;
}
}
8.9 本章作业
- 判断下面代码输出什么?
package chapter9.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task01 {
public static void main(String[] args) {
Car car = new Car();
Car car1 = new Car(100);
System.out.println(car);
System.out.println(car1);
}
}
class Car{
double price=20;
static String color="white";
@Override
public String toString() {
return price +"\t"+color;
}
public Car() {
this.price=19;
this.color="red";
}
public Car(double price) {
this.price = price;
}
}
/*
输出:
19.0 red
100.0 red
*/
- 编程题
(1). 在Frock类中声明私有的静态属性currentNum[int类型],初始值为100000,作为衣服出厂的序列号起始值。
(2). 声明共有的静态方法getNextNum,作为生成上衣序列号的方法。每调用一次,将currentNum增加100,并作为返回值。
(3). 在TestFrock类的main方法中,分别调用getNextNum方法,获取序列号并打印输出。
(4). 在Frock类中声明serialNumber(序列号)属性,并提供对应的get方法;
(5). 在Frock类中的构造器中,通过调用getNextNum方法为Frock对象获取唯一序列号,赋给serialNumber属性
(6). 在TestFrock类的main方法中,分别创建三个Frock对象,并打印三个对象的序列号,验证是否为按100递增。
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Frock {
private static int currentNum=100000;//衣服出厂序列号
private int serialNumber;
public Frock() {
serialNumber=getNextNum();
}
public static int getNextNum(){
currentNum+=100;
return currentNum;
}
public int getSerialNumber() {
return serialNumber;
}
}
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class TestFrock {
public static void main(String[] args) {
System.out.println(Frock.getNextNum());
System.out.println(Frock.getNextNum());
Frock frock = new Frock();
System.out.println(frock.getSerialNumber());
Frock frock1 = new Frock();
System.out.println(frock1.getSerialNumber());
Frock frock2 = new Frock();
System.out.println(frock2.getSerialNumber());
}
}
/*
100100
100200
100300
100400
100500
*/
- 编程题
按要求实现下列问题:
(1). 动物类Animal包含了抽象方法shout();
(2). Cat类继承了Animal,并实现了shout,打印“猫会喵喵~~~”
(3). Dog类继承了Animal,并实现了shout,打印“狗会汪汪~~~”
(4). 在测试类中实例化对象Animal cat =new Cat(),并调用cat的shout方法
(5). 在测试类中实例化对象Animal dog=new Dog(),并调用dog的shout方法
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task03 {
public static void main(String[] args) {
Animal cat=new Cat();
cat.shout();
Animal dog=new Dog();
dog.shout();
}
}
abstract class Animal{
public abstract void shout();
}
class Cat extends Animal{
@Override
public void shout() {
System.out.println("猫会喵喵~~~");
}
}
class Dog extends Animal{
@Override
public void shout() {
System.out.println("狗会汪汪~~~");
}
}
/*
猫会喵喵~~~
狗会汪汪~~~
*/
- 编程题
(1). 计算器接口具有work方法,功能是运算,有一个手机类CellPhone,定义方法testWork测试计算功能,调用计算接口的work方法
(2). 要求调用CellPhone对象的testWork方法,使用上匿名内部类
package chapter8.work_;
import java.util.Scanner;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task04 {
public static void main(String[] args) {
CellPhone cellPhone = new CellPhone();
cellPhone.testWork(new Calculater() {
@Override
public double work(double a, double b) {
return a*b;
}
});
}
}
interface Calculater{
double work(double a,double b);
}
class CellPhone{
public void testWork(Calculater calculater){
Scanner scanner = new Scanner(System.in);
double a=scanner.nextInt();
double b=scanner.nextInt();
double ans=calculater.work( a, b);
System.out.println(ans);
}
}
/*
输入:
12
21
输出:
252.0
*/
- 编程题(内部类)
(1). 编写一个类,在类中定义局部内部类B,B中有一个私有name,有一个方法show()打印name。进行测试。
(2). 进阶:A中也定义一个私有的变量name,在show方法中打印测试
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task5 {
public static void main(String[] args) {
A a = new A();
a.test();
}
}
class A{
private String name="李白";
public void test(){
class B{
private String name="杜甫";
public void pri(){
System.out.println("B name="+name);
System.out.println("A name="+A.this.name);
}
}
B b = new B();
b.pri();
}
}
/*
B name=杜甫
A name=李白
*/
- 编程题
(1). 有一个交通工具接口类Vehicles,有work方法
(2). 有Horse类和Boat类分别实现Vehicles
(3). 创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat
(4). 有Person类,有name和Vehicles属性,在构造器中为两个属性赋值
(5). 实例化Person对象“唐僧”,要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具
Task06类
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task06 {
public static void main(String[] args) {
Person tang = new Person("唐僧", null); // 初始时没有交通工具
tang.passRiver(); // 使用Boat过河
tang.common(); // 使用Horse一般行驶
tang.passRiver(); // 再次使用Boat过河
tang.passRiver(); // 继续使用Boat过河
tang.passRiver(); // 继续使用Boat过河
tang.common(); // 再次使用Horse一般行驶
}
}
/*
过河的时候 使用Boat......
一般情况下 使用Horse.....
过河的时候 使用Boat......
过河的时候 使用Boat......
过河的时候 使用Boat......
一般情况下 使用Horse.....
*/
Person类
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Person {
private String name;
private Vehicles vehicle;
// 一般情况下使用Horse作为交通工具
public void common(){
//既然调用common,即要使用Horse的work方法,那么这里需要判断:
// 如果不是一个Horse,即是null或者是Boat,就创建Horse对象,去调用Horse的work,
//如果已经是Horse对象,也可以避免再次创建Horse对象,直接使用Horse对象的work方法即可
if (!(vehicle instanceof Horse)){
vehicle = VehicleFactory.createHorse();//向上转型
}
vehicle.work();// 多态:调用实际对象的work()方法
}
// 遇到大河时使用Boat作为交通工具
public void passRiver(){
if (!(vehicle instanceof Boat)){
vehicle = VehicleFactory.createBoat();
}
vehicle.work();
}
public Person(String name, Vehicles vehicle) {
this.name = name;
this.vehicle = vehicle;
}
}
VehicleFactory类
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
// 交通工具工厂类
public class VehicleFactory {
// 创建静态马的方法
//众所周知,在西行时,马是只有一个。所以这里可以用饿汉式
public final static Horse horse=new Horse();
private VehicleFactory() { }
public static Horse createHorse() {
return horse;
}
// 创建静态船的方法
public static Boat createBoat() {
return new Boat();
}
}
Horse类
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Horse implements Vehicles{// Horse类实现Vehicles接口
@Override
public void work() {
System.out.println("一般情况下 使用Horse.....");
}
}
Boat类
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Boat implements Vehicles{// Boat类实现Vehicles接口
@Override
public void work() {
System.out.println("过河的时候 使用Boat......");
}
}
Vehicles接口
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public interface Vehicles {// 定义交通工具接口
void work(); // 执行交通工具行驶的方法
}
- 编程题(内部类)
有一个Car类,有属性temperature(温度),车内有Air(空调)类,有吹风的功能flow,Air会监视车内的温度,如果温度超过40度则吹冷气。如果温度低于0度则吹暖气,如果在这之间则关掉空调。实例化具有不同温度的Car对象,调用空调的flow方法,测试空调吹的风是否正确。
类Car
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Car {
private double temperature;
public Car(double temperature) {
this.temperature = temperature;
}
class Air{
public void flow(){
if(temperature>40){
System.out.println("温度超过40 吹冷风......");
}else if(temperature>0){
System.out.println("温度在0-40 关闭空调.....");
}else{
System.out.println("温度低于0 吹暖风.......");
}
}
}
public Air getAir(){
return new Air();
}
}
测试类Task07
package chapter8.work_;
/**
* @aim java基础学习
* @note java笔记
*/
public class Task07 {
public static void main(String[] args) {
Car car = new Car(90);
car.getAir().flow();
}
}
//温度超过40 吹冷风......