十、 面向对象 - 高级(完结)
十、 面向对象 - 高级
10.1 类变量和类方法
10.1.1 类变量的引入
堆雪球问题:有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?,编写程序解决。
传统解决方案:main中定义count变量,调用一次game方法count++
问题:
- count变量跟对象脱离, 无法数据共享
- 难以其他类访问
解决方案:需要count变量能共享 -- 引入类变
10.1.2 类变量快速入门
package com.hspedu.static_.ChildGame;
/**
* @author: Carl Zhang
* @create: 2021-11-15 09:06
*/
public class ChildGame {
public static void main(String[] args) {
//有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?,编写程序解决。
//将count用静态变量表示
Child child = new Child("赵四");
child.build();
Child child1 = new Child("马大帅");
child1.build();
Child child2 = new Child("范德彪");
child2.build();
System.out.println("共有" + Child.count + "个小孩在玩堆雪人");
System.out.println("child.count = " + child.count); //3
System.out.println("child1.count = " + child1.count); //3
System.out.println("child2.count = " + child2.count); //3 静态变量的值被所有类对象共享
}
}
class Child {
String name;
//小孩数,调用一次堆雪人方法就++
//类变量 可以通过 类名.count 访问
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void build() {
System.out.println(name + "加入了堆雪人");
count++;
}
}
10.1.3 类变量的内存分析
结论:
- 类变量(静态变量)的位置与
jdk
版本有关 - 不论类变量(静态变量)在哪,都是被同一个类的所有对象共享的,所以不影响使用
- 类变量(静态变量)随类加载而产生
10.1.4 类变量的介绍
介绍:类变量(静态变量):即 static 修饰的变量,值被所有该类对象共享
语法:
访问修饰符 static 变量名
推荐用static 访问修饰符 变量名
使用场景 :当需要一个变量该类所有对象共享时
10.1.5 如何访问
类名.变量名
推荐用 (因为类变量是随类加载而产生的,所以不受对象是否创建影响)对象名.变量名
- 前提是满足访问修饰符的权限和范围
10.1.6 类变量注意事项和使用细节
- 声明:加了static就是类变量,不加就是普通属性
- 值:类变量的值被所有类对象共享,普通属性是每个对象独享
- 访问:类变量可以不用创建对象直接访问,普通属性必须创建对象
- 产生时间:类变量随类加载而产生并完成初始化
- 生命周期:类变量随类加载而产生,随类消亡而销毁
10.1.7 类方法快速入门
介绍:static 修饰的方法就是类方法
语法:
访问修饰符 static 返回值类型 方法名() {}
推荐static 访问修饰符 返回值类型 方法名() {}
10.1.8 类方法的调用
类名.方法名()
推荐对象.方法名()
- 前提是满足访问修饰符的权限和范围
10.1.9 类方法使用场景
当方法与类中其他的非静态成员无关联,且希望能 类.方法名() 直接调用,就可以声明为静态方法
如:
- 工具类中的方法:Math类等
- 在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等
10.1.10 类方法注意事项和使用细节
- 加载:类方法和普通方法都是随着类的加载而加载,并存入方法区
- 方法的调用:
- 类方法可以通过
类名.方法名
调用,也可以通过对象.方法名
调用 - 普通方法只能通过
对象.方法名
来调用
- 类方法可以通过
- 方法中关键字:类方法中不能使用
super
和this
关键字,普通方法中可以 - 访问其他成员:
- 静态方法只能访问静态成员
- 非静态方法可以访问静态成员和非静态成员 (前提是满足访问修饰符的权限和范围)
- 重写:类方法不能被子类重写,因为它是类的成员,跟类绑定了
10.2 main 方法详解
10.2.1 main 方法的组成
10.2.2 注意事项
- 因为是静态方法,所以 main 方法不能访问当前类的其他非静态成员,只能通过创建对象来调用非静态成员
10.2.3 args动态传值
IDEA如何填 args 数组 参数(了解)
10.3 代码块
10.3.1 代码块的介绍
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过包围起来。
- 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
10.3.2 代码块基本语法
语法:static { //逻辑语句 };
- 代码块分为静态代码块(加 static )和普通代码块(不加 static )
;
号可不写
好处:
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用
10.3.3 代码块注意事项和使用细节
细节一:静态代码块和普通代码块的加载和执行顺序
- 静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次
- 类加载的时间(重要):
- 创建对象时
- 创建子类对象时,父类先加载
- 访问类中的静态成员时
- 普通代码块是在创建对象时调用的,创建一次,调用一次
package com.hspedu.codeblock;
/**
* @author: Carl Zhang
* @create: 2021-11-15 14:28
*/
public class CodeBlock {
static {
System.out.println("CodeBlock类的静态代码块");
}
{
System.out.println("CodeBlock类的非静态代码块");
}
public static void main(String[] args) {
CodeBlock codeBlock = new CodeBlock(); //CodeBlock类的非静态代码块
Person.PNum = 1; //Person类的静态代码块
Person person = new Person("赵四", 11); //Person类的非静态代码块
Person.PNum = 2; //静态代码块只执行一次
}
}
class Person {
String name;
int age;
public static int PNum;
static {
System.out.println("Person类的静态代码块");
};
{
System.out.println("Person类的非静态代码块");
} //; 可不写
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
细节二:创建一个对象时,在类中的调用顺序
- 先加载类
- 调用静态代码块和初始化静态属性
- 注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用
- 创建对象
- 调用普通代码块和普通属性的初始化
- 注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用
- 调用构造方法显示初始化属性
细节三:构造方法的补充:构造器的最前面其实隐含了 super()
和调用普通代码块
package com.hspedu.codeblock;
/**
* @author: Carl Zhang
* @create: 2021-11-16 11:26
*/
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
new BBB();
}
}
class A {
{ //step4
System.out.println("A 普通代码块 01");
}
private int n2 = getN2(); //step5
static { //step 1
System.out.println("A 静态代码块 01");
}
private static int n1 = getN1(); //step 2
public static int getN1() { //step 3
System.out.println("getN1 被调用...");
return 100;
}
public int getN2() {
System.out.println("getN2 被调用...");
return 200;
}
public A() { //step6
System.out.println("A() 构造器被调用");
}
}
class AAA { //父类 Object
{
System.out.println("AAA 的普通代码块"); //step4
}
public AAA() {
//super(); 隐藏语句 //step2 ,父类Object类
//调用普通代码块 //step3
System.out.println("AAA() 构造器被调用...."); //step5
}
}
class BBB extends AAA {
{
System.out.println("BBB 的普通代码块..."); //step7
}
public BBB() {
//super(); 隐藏语句 //step1
//调用普通代码块 //step6
System.out.println("BBB() 构造器被调用...."); //step8
}
}
细节四:创建子类对象时,在类中的调用顺序:
- 先加载父类(静态属性和静态代码块按定义顺序加载)
- 再加载子类(静态属性和静态代码块按定义顺序加载)
- 创建子类对象,调用子类构造
- 隐式调用
super()
先执行父类构造 , 父类构造里隐藏了先加载普通属性和普通代码块(按定义顺序) - 子类构造隐藏了先加载普通属性和普通代码块 (按定义顺序)
- 执行子类构造的显示语句
- 隐式调用
细节五:代码块中的范围权限和范围:静态代码块只能范围静态成员,普通代码块能范围任意成员
package com.hspedu.codeblock;
/**
* @author: Carl Zhang
* @create: 2021-11-16 11:49
*/
public class CodeBlockDetail04 {
public static void main(String[] args) {
//先加载父类(静态属性和静态代码块按定义顺序加载)
//再加载子类(静态属性和静态代码块按定义顺序加载)
//创建子类对象,调用子类构造
// 先执行父类构造 , 父类构造里隐藏了先加载普通属性和普通代码块(按定义顺序)
// 子类构造隐藏了先加载普通属性和普通代码块(按定义顺序)
// 执行子类构造的显示语句
new B02(); //(1) getVal01() (2).A02 静态代码块.. (3)getVal03 (4)B02 静态代码块.. (5) A02 普通代码块
//(5) getVal02 (6) A02 构造器 (6) getVal04 (7) B02 普通代码块 (8)B02 的构造器
System.out.println("=======================");
//范围权限和范围:
//静态代码块只能范围静态成员
//普通代码块能范围任意成员
new C02(); //200 100 100
}
}
class A02 { //父类
private static int n1 = getVal01(); //step1
static { //step3
System.out.println("A02 的一个静态代码块..");
}
{ //step11
System.out.println("A02 的第一个普通代码块..");
}
public int n3 = getVal02();//普通属性的初始化 //step12
public static int getVal01() { //step2
System.out.println("getVal01");
return 10;
}
public int getVal02() { //step13
System.out.println("getVal02");
return 10;
}
public A02() { //step09
//隐藏super()
//普通代码块 普通属性 //step10
System.out.println("A02 的构造器"); //step14
}
}
class B02 extends A02 {
private static int n3 = getVal03();//step4
static { //step6
System.out.println("B02 的一个静态代码块..");
}
public int n5 = getVal04(); //step15
{ //step17
System.out.println("B02 的第一个普通代码块..");
}
public static int getVal03() { //step5
System.out.println("getVal03");
return 10;
}
public int getVal04() { //step16
System.out.println("getVal04");
return 10;
}
public B02() { //step07
//隐藏 super() //step8
//普通代码块 普通属性初始化
System.out.println("B02 的构造器"); //step18
}
}
class C02 {
private int n1 = 100;
private static int n2 = 200;
private void m1() {
}
private static void m2() {
}
static {
//System.out.println(n1); //报错
//m1(); //报错
m2();
System.out.println(n2);
}
{
System.out.println(n1);
System.out.println(n2);
m1();
m2();
}
}
10.3.4 练习
public class CodeExercise02 {
public static void main(String[] args) {
Test test = new Test(); //运行结果?
}
}
class Sample {
Sample(String s) {
System.out.println(s); //1 //3
}
Sample() {
System.out.println("Sample 默认构造函数被调用");
}
}
class Test {
Sample sam1 = new Sample("sam1 成员初始化"); //
static Sample sam = new Sample("静态成员 sam 初始化 "); //
static {
System.out.println("static 块执行"); //2
if (sam == null) System.out.println("sam is null");
}
Test() {
//super();
//属性
System.out.println("Test 默认构造函数被调用");
}
}
10.4 单例设计模式
10.4.1 设计模式的介绍
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索
10.4.2 单例模式的介绍
单例模式:
- 单例,即单个实例。采取一定的方法使程序运行开始到结束时某一个类中只产生一个实例,并且该类只提供一个取得该类的方法。一般核心的类会这样设计
- 单例模式有
饿汉式
和懒汉式
10.4.3 单例模式应用实例
设计步骤:
- 私有化构造器,防止
new
对象 - 类的内部创建一个对象
- 向外提供一个静态方法,返回对象
public class SingleTon01 {
public static void main(String[] args) {
System.out.println(Girlfriend.girlNum); //1
Girlfriend instance = Girlfriend.getInstance();
System.out.println(instance);
}
}
class Girlfriend {
private String name;
public static int girlNum = 1;
//饿汉式:类加载时就会产生对象,即不用这个对象只访问里面的成员的时候也会产生 可能造成资源浪费
//1.私有化构造器 防止直接new
private Girlfriend(String name) {
System.out.println("Girlfriend构造器被调用");
this.name = name;
}
//2.提供一个静态化属性
//类加载时对象就会自动创建
private static Girlfriend girlfriend = new Girlfriend("小红");
//3.提供获取对象的方法
public static Girlfriend getInstance() {
return girlfriend;
}
@Override
public String toString() {
return "Girlfriend{" +
"name='" + name + '\'' +
'}';
}
}
public class SingleTon02 {
public static void main(String[] args) {
Cat instance = Cat.getInstance();
System.out.println(instance);
Cat instance1 = Cat.getInstance();
System.out.println(instance1);
System.out.println(instance == instance1);
}
}
class Cat {
private String name;
public static int catNum = 1;
//懒汉式 -- 需要使用的时候才会创建对象(多个线程同时调用方法时可能会产生多个对象,有线程安全问题)
//1.私有化构造器
private Cat(String name) {
this.name = name;
}
//2.声明一个静态的对象引用,不创建对象
private static Cat cat;
//3.提供一个公共的静态方法,返回对象
//只有当对象第一次调用getInstance方法才会创建对象,后面再次调用就会返回之前的对象,保证了单例
public static Cat getInstance() {
if (cat == null)
cat = new Cat("小白");
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
10.4.5 饿汉式和懒汉式的比较
- 最基本的区别就是创建对象的时机不同:饿汉式是类加载时就创建对象,懒汉式是调用
getInstance
方法时才创建对象 - 线程问题:懒汉式会有线程安全问题,饿汉式没有(线程安全问题看后面多线程解决)
- 资源浪费:饿汉式在类加载时就创建,即不使用对象的时候也会创建,存在资源浪费情况。懒汉式就没有
- 在我们
javaSE
标准类中,java.lang.Runtime
就是经典的单例模式。
10.5 final 关键字
10.5.1 final 基本介绍
final
中文意思:最后的,最终的.final
可以修饰类、属性、方法和局部变量- 在某些情况下,程序员可能有以下需求,就会使用到
final
- 当不希望类被继承时,可以用
final
修饰. - 当不希望类的的某个属性的值被修改,可以用
final
修饰 - 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用
final
关键字修饰 - 当不希望某个局部变量被修改,可以使用
final
修饰
- 当不希望类被继承时,可以用
10.5.2 final 的注意事项和使用细节
final
修饰的属性又叫常量,一般用XXX_XXX
来命名final
修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:(创建对象及之前)- 定义时
- 在构造器中
- 在代码块中
- 如果
final
修饰的属性是静态的,则初始化的位置只能是: (类加载时, 因为类加载时会初始化静态属性和静态代码块, 类加载时不给静态常量赋值会出现调用时为空情况)- 定义时
- 在静态代码块
- 不能在构造器中赋值
final
类不能继承,但是可以实例化对象- 一般来说,如果一个类已经是
final
类了,就没有必要再将方法修饰成final
方法。 final
不能修饰构造方法(即构造器)final
和static
往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理。- 包装类(Integer,Double,Float,Boolean等都是
final
),String也是final
类。
package com.hspedu.final_;
/**
* @author: Carl Zhang
* @create: 2021-11-17 09:50
*/
public class FinalDetail {
public static void main(String[] args) {
A2 a2 = new A2();
new A4().method01();
//修饰局部变量
final int ID;
//Boolean
//String
//Integer
//Character
//Long
System.out.println("AA.NUM = " + AA.NUM);
//System.out.println(AA.AA_ID);
}
}
// 1)final修饰的属性又叫常量,一般用XXXXXX来命名
// 2)final修饰的属性在定义时,必须赋初值,并且以后不能再修改,
// 赋值可以在如下位置之一【选择一个位置赋初值即可】:(创建对象时及之前)
// ①定义时:如public final double TAX RATE=0.08;
// ②在构造器中
// ③在代码块中。
class AA {
public static final int NUM = 3; //(7)static配合final使用,且定义时赋值,调用时不会加载类,效率更高
public final int FINAL_ID = 1; //定义时赋值;
public final int AA_NUM;
private String name;
//3)如果final修饰的属性是静态的,则初始化的位置只能是
// (类加载时, 因为类加载时会初始化静态属性和代码块,先于对象的初始化, 类加载时不给静态常量赋值会出现调用时为空情况)
// ①定义时②在静态代码块 不能在构造器中赋值。
public static final double AA_ID;
static {
System.out.println("AA类被加载....");
//AA_NUM = 1; //代码块中赋值
AA_ID = 1;
}
//final AA() { //(6)final不能修饰构造器
//}
AA(int num) {
name = "AA";
AA_NUM = 1; //构造器中赋值
//AA_NUM = num; //构造器中赋值
}
}
//4)final类不能继承,但是可以实例化对象。[A2类]
final class A2 {
}
//5)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。[A3类]
class A3 {
public void method01() {
System.out.println("A3类的method01方法");
}
}
class A4 extends A3 {
}
10.6 抽象类 abstract
10.6.1 抽象类的引入
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,用 abstract
修饰,那么这个类就是抽象类
public class Abstract01 {
}
abstract class Animal { //含抽象方法的类必须声明为抽象类
private String name;
public Animal(String name) {
this.name = name;
}
////吃东西
//public void eat() {
// System.out.println("吃东西"); //每种动物吃什么不确定的,子类还是要重写
//}
//设计成抽象方法,即没有具体实现(方法体)的方法,让子类去实现
public abstract void eat();
}
10.6.2 抽象类的介绍
abstract
修饰类,这个类就叫抽象类。语法:访问修饰符 abstract 类名{ ... }
abstract
修饰方法,这个方法就叫抽象方法。语法:访问修饰符 abstract 返回值类型 方法名(参数列表);
- 抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
- 抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
10.6.3 抽象类的注意事项和使用细节
- 抽象类不能被实例化
- 抽象类不一定要包含
abstract
方法。也就是说,抽象类可以没有abstract
方法 - 一旦类包含了
abstract
方法,则这个类必须声明为abstract
abstract
只能修饰类和方法,不能修饰属性和其它的。
package com.hspedu.abstract_;
/**
* @author: Carl Zhang
* @create: 2021-11-17 11:25
*/
public class AbstractDetail01 {
public static void main(String[] args) {
//A a = new A();//报错
}
}
//1)抽象类不能被实例化[举例]
//2)抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法[举]
abstract class A {
public void speak() {
System.out.println("A类的speak方法");
}
}
//3)一旦类包含了abstract方法,则这个类必须声明为abstract[说明]
//4)abstract 只能修饰类和方法,不能修饰属性和其它的。[说明]
abstract class B {
//public abstract String name = "B"; //报错
public abstract void speak(); //一般用这种
abstract public void speak2();
public void method1() {
//abstract int num = 1; //报错
}
//abstract {} //报错
}
- 抽象类可以有任意成员(抽象类本质还是类),比如:非抽象方法、构造器、静态属性等等
- 抽象方法不能有主体,即不能实现
- 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为
abstract
类 - 抽象方法不能使用 private 、final 和 static 来修饰,因为这些关键字都是和重写相违背的。
package com.hspedu.abstract_;
/**
* @author: Carl Zhang
* @create: 2021-11-17 11:41
*/
public class AbstractDetail02 {
public static void main(String[] args) {
System.out.println("AA.num = " + AA.num); //AA的静态代码块 AA.num = 1
}
}
//5)抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等[举例]
abstract class AA {
public static int num = 1;
public String name = "AA";
static {
System.out.println("AA的静态代码块");
}
AA() {
System.out.println("AA的构造器");
}
public void speak() {
System.out.println(name);
}
public abstract void speak2(); //6)抽象方法不能有主体,即不能实现.
public static void speak0(){}
}
//7)如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类。[举例A类,B类,C类]
class BB extends AA {
@Override
public void speak2() {
}
//@Override 报错
//public static void speak0(){}
}
abstract class CC extends AA { //CC类也是抽象类
//8)抽象方法不能使用private、final 和static来修饰,因为这些关键字都是和重写相违背的。
abstract void speak3();
//private abstract void speak4();
//final abstract void speak5();
//static abstract void speak6();
}
10.7 抽象类的最佳实践 - 模板设计模式
10.7.1 基本介绍
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
10.7.2 模板设计模式能解决的问题
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式.
10.7.3 实践案例
需求:有多个类,完成不同的任务 job 要求统计得到各自完成任务的时间
思路分析:
- 有多个类,完成不同的任务job
- 每个类都有job方法,各类的实现不同 ,将job方法抽象出来,写在父类,让子类去实现
- 要求统计得到各自完成任务的时间
- 计算时间:得到系统时间,调用完各自 job 后再获取一次系统时间,两次时间之差就是完成任务时间(这部分相同的,写在父类)
- 调用不同的 job -- 动态绑定机制
package com.hspedu.template_;
/**
* @author: Carl Zhang
* @create: 2021-11-17 14:31
*/
public class TestTemplate02 {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
System.out.println("1--100000000的奇数: " + aa.getProduct());
BB bb = new BB();
bb.calculateTime();
System.out.println("1--100000000的和: " + bb.getSum());
}
}
//1)有多个类,完成不同的任务job
// 每个类都有job方法, 各类的实现不同 -- 写在父类, 声明抽象方法 . 子类去实现job()
//2)要求统计得到各自完成任务的时间
// 得到系统时间, 调用完各自job后再获取一次系统时间 两次时间之差就是完成任务时间 -- 相同的, 写在父类
// 调用各自job -- 动态绑定机制
abstract class Template02 {
public abstract void job(); //声明抽象的job让子类去实现
//利用动态绑定, 统计每个子类对象调用job方法的时间
public void calculateTime() {
//获取系统时间
long starTime = System.currentTimeMillis();
job(); //动态绑定机制
//获取结束时间
long endTime = System.currentTimeMillis();
//两个时间相减就是执行任务所用时间
System.out.println("执行任务所用时间: " + (endTime - starTime) + "毫秒");
}
}
class AA extends Template02 {
private int count = 1;
public int getProduct() {
return count;
}
/**
* 累计1-100000000的奇数
*/
@Override
public void job() {
for (long i = 1; i <= 100000000; i++) {
if (i % 2 != 0 )
count++;
}
}
}
class BB extends Template02 {
private long sum = 1;
public long getSum() {
return sum;
}
/**
* 计算 1 - 100000000以内的和
*/
@Override
public void job() {
for (long i = 0; i < 100000000; i++) {
sum += i;
}
}
}
10.8 接口
10.8.1 接口的介绍
接口即制定的规范,规则。
10.8.2 接口的语法
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
语法:
访问修饰符 Interface 接口名 { //访问修饰符只能是 public 或默认不填
属性;
抽象方法;
}
//类实现接口
访问修饰符 class implements 接口名 {
实现的方法;
自己的属性;
自己的方法;
静态方法(){} //jDK8增加
默认方法(){} //JDK8增加
}
小结:
- 接口是更加抽象的类,抽象类里的方法可以有方法体,接口里的所有方法都没有方法体
jdk7.0
。接口体现了程序设计的多态和高内聚低偶合的设计思想。 - 特别说明:
**Jdk8.0**
后接口类可以有静态方法,默认方法 (default
修饰),也就是说接口中可以有方法的具体实现
/**
* @author carl
*/
public interface Demo {
/**
* 接口的默认方法
*/
default void method1() {
System.out.println("这是接口的默认方法");
}
/**
* 接口的静态方法
*/
static void method2() {
System.out.println("这是接口的静态方法");
}
/**
* 接口的抽象方法
*/
void method3();
}
class DemoClass implements Demo{
@Override
public void method3() {
Demo.super.method1(); //调用指定父接口的抽象方法
Demo.method2(); //调用接口的静态方法
System.out.println("DemoClass实现的method3");
}
public static void main(String[] args) {
new DemoClass().method3();
}
}
10.8.3 接口的应用场景
10.8.4 接口和继承的比较
看一个案例:
- 悟空和老猴子的关系?
- 悟空想要想鸟一样飞,想鱼一样游?
public class Monkey {
private String name;
public String getName() {
return name;
}
public Monkey(String name) {
this.name = name;
}
public void climb() {
System.out.println(name + "在爬树");
}
}
public interface Fish {
void swimming();
}
public class Wukong extends Monkey implements Fish {
public Wukong(String name) { //悟空继承了猴子
super(name);
}
//实现接口,重写swimming方法
@Override
public void swimming() { //悟空想要游泳,通过实现鱼接口扩展
System.out.println(getName() + "在游泳");
}
}
public class ExtendsVsInterface {
public static void main(String[] args) {
Wukong wukong = new Wukong("孙悟空");
wukong.climb();
wukong.swimming();
//小结: 当子类继承了父类,就自动的拥有父类的功能
//如果子类需要扩展功能,可以通过实现接口的方式扩展.
//可以理解 实现接口 是 对 java 单继承机制的一种补充
}
}
- 接口和继承解决的问题不同
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活
- 接口比继承更加灵活
- 继承是满足
is-a
的关系,而接口只需满足like-a
的关系。 - 接口在一定程度上实现代码**解耦 **(即:接口规范性+动态绑定机制)
- 继承是满足
10.8.3 注意事项和使用细节
- 接口不能被实例化
- 接口中所有的方法是
public
方法,接口中抽象方法,可以不用abstract
修饰 - 一个普通类实现接口,就必须将该接口的所有抽象方法都实现。
alt + enter快捷键
- 抽象类实现接口,可以不用实现接口的方法。
public class InterfaceDetail01 {
public static void main(String[] args) {
//IA ia = new IA();//报错
}
}
public interface IA {
public abstract void say01();
//protected void say02(); 报错
void hi(); //实际上是 public abstract....
}
public abstract class Tiger implements IA{
//可以不实现IA中的抽象方法
}
- 一个类同时可以实现多个接口
- 接口中的属性,只能是
final
的,而且是public static final
修饰符。- 比如:
int a=1
;实际上是public static final int a=1;
(必须初始化)
- 比如:
- 接口中属性的访问形式:
接口名.属性名
- 接口不能继承其它的类,但是可以继承多个别的接口
interface IC extends IB,IA {}
- 接口的修饰符只能是
public
和默认
,这点和类的修饰符是一样的。
public class InterfaceDetail02 {
public static void main(String[] args) {
System.out.println(IB.i);
//IB.i = 2; //不能给final变量赋值'
}
}
public class Pig implements IB,IC{ //1.一个类实现多个接口
@Override
public void hello() {
}
@Override
public void say() {
}
@Override
public void say01() {
}
@Override
public void hi() {
}
}
public interface IB {
public void hello();
int i = 1; //其实是public static final
}
public interface IC extends IB,IA{
public void say();
}
10.8.4 接口的多态性
- 多态参数:接口引用可以指向实现了接口的类的对象,(看下面案例)
public class InterfacePolyParameter {
public static void main(String[] args) {
Computer computer = new Computer();
computer.work(new Phone_()); //电脑usb插上手机工作
computer.work(new Camera()); //相当于 IF anIf = new Camera(); 接口引用指向实现了接口的类的对象
}
}
interface Usb {
void start();
void close();
}
class Phone_ implements Usb {
@Override
public void start() {
System.out.println("连接手机");
}
@Override
public void close() {
System.out.println("断开手机连接");
}
}
class Camera implements Usb {
@Override
public void start() {
System.out.println("连接相机");
}
@Override
public void close() {
System.out.println("关闭相机连接");
}
}
class Computer {
public void work(Usb usb) { //在电脑上插入usb和断开usb 可传入实现了Usb接口的类的对象,多态参数的体现
usb.start();
System.out.println("开始传数据");
usb.close();
}
}
- 多态数组
- 案例:在 Usb 数组中,存放 Phone 和相机对象,Phone 类还有一个特有的方法法 call(),请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外,还需要调用 Phone 特有方法 call()
//直接在上个案例的手机类上修改
class Phone_ implements Usb {
@Override
public void start() {
System.out.println("连接手机");
}
@Override
public void close() {
System.out.println("断开手机连接");
}
//特有方法call
public void call(int teleNum) {
System.out.println("正在呼叫" + teleNum);
}
}
public class InterfacePoly {
public static void main(String[] args) {
//演示一个案例:给Usb数组中,存放 Phone和相机对象,Phone类还有一个特有的方法call
//(),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone 特有方法call.
Usb[] usbs = new Usb[4];
usbs[0] = new Phone_();
usbs[1] = new Camera();
usbs[2] = new Camera();
usbs[3] = new Phone_();
for (int i = 0; i < usbs.length; i++) {
usbs[i].start();
usbs[i].close();
if (usbs[i] instanceof Phone_)
((Phone_) usbs[i]).call(139); //将指向Phone_类对象的引用向下转型
}
}
}
- 接口存在多态传递现象
public class InterfacePolyPass {
public static void main(String[] args) {
/**
* 接口的引用可以执行被实现类的对象
* 接口BB继承了AA接口,TestBB实现了BB接口,相当于同时也要实现AA接口
* 所以接口aa引用可以指向TestBB类的对象,这就是多态传递
*/
AA aa = new TestBB();
aa.hi();
}
}
interface AA {
void hi();
}
interface BB extends AA{}
class TestBB implements BB{
@Override
public void hi() {
System.out.println("TestBB的hi方法被调用");
}
}
10.8.5 类的进一步完善
10.9 内部类 InnerClass
10.9.1 基本介绍
- 一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类
inner class
,嵌套其他类的类称为外部类outer class
。是我们类的第五大成员【思考:类的五大或员是哪些?[属性、方法、构造器、代码块、内部类]】
- 内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
- 注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类。
10.9.2 基本语法
public class InnerClass01 { //外部其他类
public static void main(String[] args) {
}
}
class Outer{ //外部类
private String name;
public void show() { //方法
System.out.println("show方法");
}
public Outer(String name) { //构造器
this.name = name;
}
{ //代码块
System.out.println("代码块");
}
class Inner { //内部类
}
}
10.9.3 内部类的分类
- 定义在局部位置(局部方法,局部代码块)
- 局部内部类(有类名)
- 匿名内部类(无类名)
- 定义在成员位置(成员方法,成员代码块)
- 成员内部类(无
static
修饰) - 静态内部类(有
static
修饰)
- 成员内部类(无
10.9.4 局部内部类 LocalInnerClass
语法: class 外部类{ 普通方法/代码块{ class 内部类{} } }
见案例。
- 局部内部类是定义在外部类的局部位置,比如方法中,并且有类名,本质是一个类
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符 (局部内部类相当于局部变量)。但是可以使用final 修饰
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类---访问---->外部类的成员访问方式:直接访问
- 外部类---访问---->局部内部类的成员访问方式:创建对象,再访问(注意:必须在作用域内)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
package com.hspedu.innerclass;
/**
* @author: Carl Zhang
* @create: 2021-11-18 11:36
*/
public class InnerClassDetail01 {
public static void main(String[] args) {
Outer02 outer02 = new Outer02();
outer02.method02();
System.out.println("outer02.hashCode() = " + outer02.hashCode()); //460141958
}
}
class Outer02 {
private int id = 02;
private String name = "Outer02";
private void method03() {
System.out.println("Outer03的method03");
//new Inner02(); //不在作用域,无法访问局部内部类
}
public void method02() { //局部方法
final String name = "method02";
//1.局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
//3.不能添加访问修饰符(局部内部类相当于局部变量)。但是可以使用final 修饰
//4.作用域:仅仅在定义它的方法或代码块中。 -- 局部变量的作用域也是在定义的方法或代码块中
final class Inner02 { //局部内部类本质就是一个类,可以有类的五大成员
private String name = "Inner02";
//2.可以直接访问外部类的所有成员,包含私有的
public void innerMethod03() {
//5.局部内部类---访问---->外部类的成员[访问方式:直接访问]
System.out.println(id); //访问外部类的属性
method03(); //访问外部类的方法
//7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问【演示】
//Outer02.this 相当于 外部类的对象,谁调用Inner02 就代表谁
System.out.println("Outer02.this.name = " + Outer02.this.name);
System.out.println("Outer02.this.hashCode() = " + Outer02.this.hashCode());//460141958
System.out.println("this.name = " + this.name); //Inner02 this 相当于这个局部内部类的对象,
// 谁调用innerMethod03方法就代表谁
}
}
//class inner03 extends inner02{} //final修饰的类无法继承
//6.外部类---访问---->局部内部类的成员访问方式:创建对象,再访问(注意:必须在作用域内)
Inner02 inner02 = new Inner02();
inner02.innerMethod03();
}
{
class inner04{ //代码块中的局部内部类
}
}
// 记住:
// (1)局部内部类定义在方法中/代码块
// (2)作用域在方法体或者代码块中
// (3)本质仍然是一个类
}
10.9.5 🔴匿名内部类 Anonymous
匿名内部类:Anonymous(非常重要,源码和框架常见)
语法:
- 匿名内部类是定义在外部类的局部位置(普通方法,普通代码块)的类,没有类名
- 匿名内部类本质是一个类,同时还是一个对象
- 匿名内部类使用一次后就没了,但是创建的对象能重复使用
package com.hspedu.innerclass;
/**
* @author: Carl Zhang
* @create: 2021-11-18 14:00
*/
public class AnonymousInnerClass { //Anonymous 匿名的
public static void main(String[] args) {
Outer03 outer03 = new Outer03();
outer03.method03();
}
}
class Outer03 { //外部类
private int id = 3;
public void method03() {
//一、基于接口的匿名内部类
//1.需求:实现接口IA,调用cry方法,且实现类只使用一次。
//2.传统法方法:创建类实现接口,重写cry方法,再创建对象,对象调用cry方
//new Tiger().cry(); //只要运行一次老虎哭的话,Tiger类还在,浪费资源
//3.通过匿名内部类来简化开发
//4.看类的编译类型和运行类型:
// 编译类型:IA
// 运行类型:匿名内部类Outer03$1
/*
* JDK底层创建了这个类 并分配类名 Outer03$1 -- Outer03外部类
* class Outer03$1 implements IA{
* @Override
* public void cry() {
* System.out.println("老虎哭....");
* }
* }
*/
//5.JDK底层创建 Outer03$1 内部类,立马就创建了这个类的对象,并把地址值传递给tiger
//6.匿名内部类使用一次后就没了,但是创建的对象能重复使用
IA tiger = new IA() { //IA接口的一个匿名内部类
@Override
public void cry() {
System.out.println("老虎哭....");
}
};
tiger.cry();
System.out.println("tiger.getClass() = " + tiger.getClass());//获取全类名 class com.hspedu.innerclass.Outer03$1
IA cat = new IA() {
@Override
public void cry() {
System.out.println("猫咪哭");
}
};
cat.cry();
System.out.println("cat.getClass() = " + cat.getClass()); // class com.hspedu.innerclass.Outer03$2
//二、基于类的匿名内部类
//分析:
//1. 编译类型 :Father
//2. 运行类型 :匿名内部类Outer03
/*
相当于底层实现了
public class Outer03$3 extends Father {
@Override
public void test() {
System.out.println("匿名内部类的test方法");
}
}
*/
//3. 底层创建Outer03$3内部类的时候,立即创建了这个类的对象,并把地址值传递给father引用
//4.右边 "马大帅" 传参传给的是Father的构造器
Father father = new Father("马大帅") {
@Override
public void test() {
System.out.println("匿名内部类的test方法");
}
};
father.test();
System.out.println("father.getClass() = " + father.getClass()); //class com.hspedu.innerclass.Outer03$3
//三、基于抽象类的匿名内部类
Animal dog = new Animal() {
@Override
void eat() {
System.out.println("狗吃肉....");
}
};
dog.eat();
System.out.println("dog.getClass() = " + dog.getClass());
}
}
interface IA {//接口
void cry();
}
//传统方法
//class Tiger implements IA {
// @Override
// public void cry() {
// System.out.println("老虎哭了.....");
// }
//}
class Father {
private String name;
public Father(String name) {
this.name = name;
System.out.println("接收的name = " + name);
}
public void test() {
System.out.println("Father类的test方法");
}
}
abstract class Animal {
abstract void eat();
}
- 匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特证,对前面代码分析可以看出这个特点,因此可以直接调用匿名内部类方法
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量
- 作用域:仅仅在定义它的方法或代码块中 -- 因为使用一次就没有了
- 外部其他类---不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
package com.hspedu.innerclass;
/**
* @author: Carl Zhang
* @create: 2021-11-18 16:08
*/
public class AnonymousInnerClassDetail {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method04();
System.out.println("outer04 = " + outer04);
}
}
class Outer04 {
private String name = "Outer04";
public void method04() {
//4.不能添加访问修饰符,因为它的地位就是一个局部变量。[过]
//5.作用域:仅仅在定义它的方法或代码块中。[过] --因为使用一次就没有了
//7.外部其他类---不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)
//2.匿名内部类的语法比较奇特,请大家注意,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,
// 因此从语法上看,它既有定义类的特征,也有创建对象的特证,对前面代码分析可以看出这个特点,
// 因此可以直接调用匿名内部类方法
//方式一 声明引用调用内部方法
AAA a1 = new AAA() {
String name = "Outer04$1";
@Override
public void methodA() {
System.out.println("声明匿名内部类对象引用调用内部方法");
//3.可以直接访问外部类的所有成员,包含私有的[案例演示]
//6.匿名内部类---访问---->外部类成员[访问方式:直接访问]
System.out.println("name = " + name);
//8.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循
//就近原则, 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println("Outer04.this.name = " + Outer04.this.name); //Outer04.this代表outer04对象
System.out.println("Outer04.this = " + Outer04.this);
}
public void methodA2() {
System.out.println("method02");
}
};
a1.methodA();
//a1.methodA2(); //向上转型后访问子类特有方法需要向下转型,而匿名内部类使用一次就没
//所以基于接口的匿名内部类无法访问内部特有方法
//方式二 直接调用内部方法 new AAA() {...} 整体就是一个对象
new AAA() {
@Override
public void methodA() {
System.out.println("匿名内部类直接调用methodA方法");
}
}.methodA();
}
}
interface AAA {
void methodA();
}
10.9.6 匿名内部类的最佳实践
- 当做实参直接传递,简洁高效
public class InnerClassExercise01 {
public static void main(String[] args) {
//调用IL接口里的 show 方法
//fi(new Picture()); //如果只调用一次这个方法实现,会浪费资源
//匿名内部类
//使用一次就没了
fi(new IL() {
@Override
public void show() {
System.out.println("这是一副名画");
}
});
}
public static void fi(IL il) {
il.show(); //动态绑定
}
}
interface IL {
void show();
}
//一、传统方法
//定义类实现接口IL,重写show方法,再创建对象调用show方法、
class Picture implements IL{
@Override
public void show() {
System.out.println("这是一副名画");
}
}
10.9.7 成员内部类 MemberInnerClass
- 说明:成员内部类是定义在外部类的成员位置,并且没有 static 修饰。
- 可以添加任意访问修饰符( public、protected、默认、private ),因为它的地位就是一个成员。
- 可以直接访问外部类的所有成员,包含私有的
- 作用域和外部类的其他成员一样,为整个类体
- 成员内部类---访问---->外部类成员(比如:属性)[访问方式:直接访问]
- 外部类---访问-->成员内部类访问方式:创建对象,再访问
- 外部其他类---访问--->成员内部类:
- 方式一:外部类中定义一个
getInstance
方法返回成员内部类对象实例,创建外部类对象调用getInstance
方法 - 方式二:直接通过外部类对象 new 内部类对象:成员内部类是外部类的一个成员,所以要创建外部类对象再访问
- 方式一:外部类中定义一个
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用
外部类名.this.成员
去访问
package com.hspedu.innerclass;
/**
* @author: Carl Zhang
* @create: 2021-11-22 09:31
*/
public class MemberInnerClass01 {
public static void main(String[] args) {
//7.外部其他类---访问--->成员内部类
//方式一:外部类中定义一个getInstance方法返回成员内部类对象实例,创建外部类对象调用getInstance方法
Outer05 outer05 = new Outer05();
Outer05.Inner05 instanceInner05 = outer05.getInstanceInner05();
instanceInner05.method05();
//方式二:直接通过外部类对象new 内部类对象:成员内部类是外部类的一个成员,所以要创建外部类对象再访问
Outer05.Inner05 inner05 = outer05.new Inner05();
inner05.method05();
}
}
class Outer05 {
private int id = 5;
private String name = "Outer05";
//成员内部类
//1.说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。
//2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public class Inner05 {
private String name = "Inner05";
public void method05() {
//3.可以直接访问外部类的所有成员,包含私有的
//5.成员内部类---访问---->外部类成员(比如:属性)[访问方式:直接访问]
System.out.println(id); //5
//8.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,
// 如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
System.out.println(name); //Inner05
System.out.println(Outer05.this.name); //Outer05
}
}
public Inner05 getInstanceInner05() {
return new Inner05();
}
public void Method05_() {
//4.作用域和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中
// 创建成员内部类对象,再调用方法.
//6.外部类---访问-->成员内部类访问方式:创建对象,再访问
Inner05 inner05 = new Inner05();
inner05.method05();
}
}
10.9.8 静态内部类 StaticInnerClass
- 说明:静态内部类是定义在外部类的成员位置,并且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员
- 作用域:同其他的成员,为整个类体
- 静态内部类---访问---->外部类(比如:静态属性)[访同方式:直接访向所有静态成员]
- 外部类---访问------>静态内部类访问方式:创建对象,再访问
- 外部其他类--访问----->静态内部类
- 方式一: 通过
外部类.getInstance方法
获得内部类对象(静态方法直接 类名.方法名) - 方式二:直接
new 外部类.内部类对象
(内部类是静态的,静态成员可以不创建对象,直接类名.成员访问)
- 方式一: 通过
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
package com.hspedu.innerclass;
/**
* @author: Carl Zhang
* @create: 2021-11-22 09:49
*/
public class StaticInnerClass {
public static void main(String[] args) {
//6.外部其他类--访问----->静态内部类
//方式一: 通过外部类.getInstance方法获得内部类对象(静态方法直接 类名.方法名)
Outer06.Inner06 instanceInner06 = Outer06.getInstanceInner06();
instanceInner06.method06();
//方式二:直接new 外部类.内部类对象(内部类是静态的,静态成员可以不创建对象,直接类名.成员访问)
Outer06.Inner06 inner06 = new Outer06.Inner06();
inner06.method06();
}
}
class Outer06 {
private static int id = 6;
private static String name = "Outer06";
//说明:静态内部类是定义在外部类的成员位置,并且有static修饰
//2.可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。
public static class Inner06 {
private static String name = "Inner06";
public void method06() {
//1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4.静态内部类---访问---->外部类(比如:静态属性)[访同方式:直接访向所有静态成员]
System.out.println(id);
//7.如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,
// 如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
System.out.println(name); //Inner06
System.out.println(Outer06.name); //Outer06
}
}
public void method06_ () {
//3.作用域:同其他的成员,为整个类体
//5.外部类---访问------>静态内部类访问方式:创建对象,再访问
Inner06 inner06 = new Inner06();
inner06.method06();
}
public static Inner06 getInstanceInner06() {
return new Inner06();
}
}
10.9.9 老韩说:我亦无他,唯手熟而
本文作者:Carl-Zhang
本文链接:https://www.cnblogs.com/Carl-Zhang/p/15763026.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步