一、类变量和类方法
1.1 类变量
1. 提出问题
有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?编写程序解决
2. 传统方法解决方案
- 在 main 方法中定义一个变量 count
- 当一个小孩加入游戏后,count++,最后输出count 就记录有多少个小孩玩游戏 ```java package com.hspedu.static_;
public class ChildGame { public static void main(String[] args) { //定义一个变量 count,统计有多少小号加入了游戏 int count = 0;
Child child = new Child("白骨精");
child.join();
count++;
Child child1 = new Child("狐狸精");
child1.join();
count++;
Child child2 = new Child("黑熊精");
child2.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 + "加入了游戏...");
}
}
问题分析:
1. count 是一个独立于对象,很尴尬
1. 以后我们访问 count 很麻烦,没有使用到 OOP
1. 因此,我们引出 类变量/静态变量
<a name="OBawa"></a>
#### 3. 类变量快速入门
思考:如果设计一个 int count 表示总人数,我们在创建一个小孩时,就把 count 加 1,并且 count 是所有对象共享的就 ok 了!我们使用类变量来解决 ChildGame.java 改进
```java
package com.hspedu.static_;
public class ChildGame {
public static void main(String[] args) {
//定义一个变量 count,统计有多少小号加入了游戏
Child child = new Child("白骨精");
child.join();
child.count++;
Child child1 = new Child("狐狸精");
child1.join();
child1.count++;
Child child2 = new Child("黑熊精");
child2.join();
child2.count++;
System.out.println("共有" + Child.count + "个小孩加入了游戏");
System.out.println(child.count);
System.out.println(child1.count);
System.out.println(child2.count);
}
}
class Child {
private String name;
public static int count;
public Child(String name) {
this.name = name;
}
public void join() {
System.out.println(name + "加入了游戏...");
}
}
4. 类变量的内存布局
注意:static 变量是对象共享,不管 static 变量在哪里,共识
- static 变量是同一个类所有对象共享
- static 类变量,在类加载的时候就生成了
5. 类变量定义
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问。
定义语法:
访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名;
6. 类变量的访问
类名.类变量名 【推荐】
对象名.类变量名
注意:静态变量的访问修饰符的访问权限和范围 与 普通属性是一样的
7. 类变量使用注意事项和细节讨论
什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):
比如:定义学生类,统计所有学生共交多少钱类变量与实例变量(普通变量)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
类变量可以通过 类名.类变量名 或者 对象名.类变量名来访问,单Java设计者推荐我们使用类名.类变量名方式访问。【须满足访问修饰符的访问权限和范围】
实例变量不能通过 类名.类变量名 方式访问
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
类变量的生命周期是随类的加载开始,随着类消亡而摧毁
1.2 类方法
1. 类方法的基本介绍
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名 () { } 【推荐】
static 访问修饰符 数据返回类型 方法名 () { }
2. 类方法的调用
使用方式:
类名.类方法名
对象名.类方法名
注意:须满足访问修饰符的访问权限和范围
3. 类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
比如:工具类中的方法 utils
Math类、Arrays类、Collections集合类
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一堆数组,冒泡排序,完成某个计算任务等
4. 类方法使用注意事项和细节讨论
类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区:
类方法中无 this 的参数
普通方法中隐藏着 this 的参数类方法可以通过类名调用,也可以通过对象名调用
普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
类方法中不允许使用和对象有关的关键字,比如 this 和 super 。普通方法可以。
类方法(静态方法)中,只能访问静态变量 或 静态方法
普通成员方法,既可以访问普通变量/方法,也可以访问静态变量/方法。
小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
5. 课堂练习
输出什么?
public class Test {
static int count = 9;
public void count() {
System.out.println(“count=” + (count++));
}
public static void main(String args[]) {
new Test().count();
new Test().count();
System.out.println(Test.count);
}
}看看下面代码有没有错误,如果有错误,就修改,看看输出什么?
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
id++;
return total;
}
public Person() {
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
System.out.println(“Number of total is “ + Person.getTotalPerson());
Person p1 = new Person();
System.out.println(“Number of total is “ + Person.getTotalPerson());
}
}看看下面代码有没有错误,如果有错误,请修改,看看total等于多少?
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total) {
this.total = total;
Person.total = total;
}
public Person() {
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3);
new Person();
}
}
二、理解main方法语法
2.1 深入理解 main方法
1. main方法解释
main方法的形式:public static void main(String[] args) { }
- main方法是虚拟机调用的
- Java虚拟机需要调用类的 main()方法没所以该方法的访问权限必须是 public
- Java虚拟机在执行 main()方法时不必创建对象,所以该方法必须是 static
- 该方法接收 String 类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
- 语法:java 执行的程序 参数1 参数2 参数3 (控制台运行)
特别提示:
- 在 main方法中,我们可以直接调用 main方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员 ```java package com.hspedu.main_;
public class Main01 { //静态的变量/属性 private static String name = “韩顺平教育”; //非静态的变量/属性 private int n1 = 10000; //静态方法 public static void hi() { System.out.println(“Main01 的 hi 方法”); } //非静态方法 public void cry() { System.out.println(“Main01 的 cry 方法”); } public static void main(String[] args) { //可以直接使用 name //1. 静态方法 main 可以访问本类的静态成员 System.out.println(“name=” + name); hi(); //2. 静态方法 main 不可以访问本类的非静态成员 //System.out.println(“n1=” + n1);//错误 //cry(); //3. 静态方法 main 要访问本类的非静态成员,需要先创建对象 , 再调用即可 Main01 main01 = new Main01(); System.out.println(main01.n1);//ok main01.cry(); } }
<a name="H4Sgc"></a>
#### 2. IDEA 中如何给 main方法传参
演示代码:
```java
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
三、代码块
3.1 基本介绍
代码化块又称初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建类时隐式调用。
3.2 基本语法
[修饰符] {
代码
};
3.3 注意事项
- 修饰符 可选,要写的话,也只能写 static
- 代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ; 号可以写上,也可以省略
- 代码块调用的顺序优先于构造器
3.4 代码块的好处
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
案例演示
```java package com.hspedu.codeblock_;
public class CodeBlock01 { public static void main(String[] args) { Movie movie = new Movie(“你好,李焕英”); System.out.println(“===============”); Movie movie2 = new Movie(“唐探 3”, 100, “陈思诚”); } }
class Movie { private String name; private double price; private String director;
//3 个构造器-》重载
//老韩解读
//(1) 下面的三个构造器都有相同的语句
//(2) 这样代码看起来比较冗余
//(3) 这时我们可以把相同的语句,放入到一个代码块中,即可
//(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
//(5) 代码块调用的顺序优先于构造器..
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正是开始...");
}
public Movie(String name) {
System.out.println("Movie(String name) 被调用...");
this.name = name;
}
public Movie(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("Movie(String name, double price, String director) 被调用...");
this.name = name;
this.price = price;
this.director = director;
}
}
<a name="rj9hP"></a>
### 3.5 代码块使用注意事项和细节讨论
1. static 代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
1. 类什么时候被加载?
1. 创建对象实例时(new)
1. 创建子类对象实例时,父类也会被加载
1. 使用类的静态成员时(静态属性,静态方法)
3. 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。静态代码块的执行优先于普通代码块。
3. 创建一个对象时,在一个类 的调用顺序是:
1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
1. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
1. 调用构造方法
5. 构造器的最前面其实隐含了 super() 和 调用普通代码块。静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
5. 我们看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
1. 父类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
1. 子类的静态代码块和静态属性初始化(优先级一样,按定义顺序执行)
1. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
1. 父类的构造方法
1. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
1. 子类的构造方法
7. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
```java
package com.hspedu.codeblock;
public class CodeBlockDetail {
public static void main(String[] args) {
// System.out.println(Cat.name);
// new Cat();
new BlackCat();
}
}
class Animal {
private static int n1 = getN1(); //静态属性初始化
private int n2 = getN2(); //普通属性初始化
{
System.out.println("Animal的普通代码块被执行...");
}
static {
System.out.println("Animal的静态代码块被执行...");
}
public Animal() {
System.out.println("Animal的构造器被执行");
}
public static int getN1() {
System.out.println("Animal的静态属性初始化...");
return 10;
}
public int getN2() {
System.out.println("Animal的普通属性初始化...");
return 20;
}
}
class Cat extends Animal {
private static int n3 = getN3(); //静态属性初始化
private int n4 = getN4(); //普通属性初始化
public Cat() {
System.out.println("Cat的构造器...");
}
{
System.out.println("Cat的普通代码块被执行...");
}
public static String name = "小花猫";
static {
System.out.println("Cat的静态代码块被执行...");
}
public static int getN3() {
System.out.println("Cat的静态属性初始化...");
return 30;
}
public int getN4() {
System.out.println("Cat的普通属性初始化...");
return 40;
}
}
class BlackCat extends Cat {
private static int n3 = getN5(); //静态属性初始化
private int n4 = getN6(); //普通属性初始化
public BlackCat() {
System.out.println("BlackCat的构造器...");
}
{
System.out.println("BlackCat的普通代码块被执行...");
}
static {
System.out.println("BlackCat的静态代码块被执行...");
}
public static int getN5() {
System.out.println("BlackCat的静态属性初始化...");
return 50;
}
public int getN6() {
System.out.println("BlackCat的普通属性初始化...");
return 60;
}
}
四、单例设计模式
4.1 什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索。
4.2 什么是单例模式
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式:
- 饿汉式
- 懒汉式
4.3 单例模式应用实例
1. 饿汉式
步骤:
- 构造器私有化
- 类的内部直接创建对象(该对象是static)
- 向外暴露一个静态的公共方法
- 代码实现 ```java package com.hspedu.single_;
public class SingleTon01 { public static void main(String[] args) { Wife instance = Wife.getInstance(); System.out.println(instance); } } //有一个类 //只能有一个实例 class Wife { private String name;
//为了能够在静态方法中,返回xb对象,需要将其修饰为 static
private static Wife xb = new Wife("小白");
//如何保证只能创建一个 Wife 对象
//步骤[单例模式 - 饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是static)
//3. 提供一个公共的接口,返回 xb对象
private Wife(String name) {
this.name = name;
}
public static Wife getInstance() {
return xb;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
'}';
}
}
说明:饿汉式 在类加载的时候就会创建对象,不管有没有使用,所以可能会造成资源的占用或浪费
<a name="lj4xL"></a>
#### 2. 懒汉式
步骤:
1. 仍然构造器私有化
1. 定义一个static 静态属性对象
1. 提供一个 public 的 static 方法,可以返回一个 Cat对象
1. 懒汉式,只有当用户使用getInstance()方法时,才返回 cat对象,再次调用时,会返回上次创建的cat对象,从而保证单例
<a name="e8t4l"></a>
#### 3. 饿汉式 VS 懒汉式
1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了实例对象,而懒汉式是在使用时才创建。
1. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
1. 饿汉式存在浪费资源的可能,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
1. 在我们JavaSE标准类中,java.lang.Runtime 就是典型的单例模式。
```java
package com.hspedu.single_;
public class SingleTon02 {
public static void main(String[] args) {
// System.out.println(Cat.num);
Cat instance = Cat.getInstance();
System.out.println(instance);
Cat instance1 = Cat.getInstance();
System.out.println(instance1);
System.out.println(instance == instance1);
}
}
class Cat {
public static int num = 100;
private String name;
private static Cat cat; //默认是 Null
//步骤
//1. 仍然构造器私有化
//2. 定义一个static 静态属性对象
//3. 提供一个 public 的 static 方法,可以返回一个 Cat对象
//4. 懒汉式,只有当用户使用getInstance()方法时,才返回 cat对象,再次调用时,会返回上次创建的cat对象,从而保证单例
private Cat(String name) {
System.out.println("构造器被调用...");
this.name = name;
}
public static Cat getInstance() {
if (cat == null) { //如果还没有创建 cat对象
cat = new Cat("小可爱");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
五、final 关键字
5.1 基本介绍
final 可以修饰类、属性、方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到 final:
- 当不希望类被继承时,可以用 final 修饰
- 当不希望父类的某个方法被子类覆盖/重写时,可以用final 关键字修饰
- 当不希望类的某个属性的值被修改,可以用final修饰
- 当不希望某个局部变量被修改,可以使用 final 修饰
5.2 final 使用注意事项和细节讨论
final 修饰的属性又叫常量,一般用 XX_XX_XX 来命名
final 修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:
【选择一个位置赋初值即可】- 定义时:如 public final double TAX_RATE = 0.08;
- 在构造器中
- 在代码块中
如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块
- 不能在构造器中赋值
final 类不能继承,但是可以实例化对象
如果类不是 final 类,但是含有 final 方法,则该方法虽然不能重写,但是可以被继承
一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法。
final 不能修饰 构造方法(即 构造器)
final 和 static 往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
包装类(Integer,Double,Float,Boolean 等都是 final),String 也是 final 类
5.3 小练习
请编写一个程序,能够计算圆形的面积。要求圆周率为3.14,赋值的位置3个方式都写一下
以下代码是否存在问题?
public class Something {
public int cal(final int x) {
++x; //错误,x 是final修饰,不能修改
return x + 1; //正确,x参与计算,并没有修改自身的值
}
}
六、抽象类 abstract
6.1 问题引入
class Animal {
private String name;
private int age;
public Animal(String name, int age) {
super();
this.name = name;
this.age = age;
}
//动物都有 eat的行为
public void eat() {
System.out.println("这是一个动物,但是目前不知道吃什么");
}
}
父类方法 的不确定性:
当父类的某些方法,需要声明,但是有不确定如何实现时,可以将其声明为抽象方法,那么这个类就是抽象类
6.2 抽象类引入
当父类的一些方法不能确定时,可以用 abstract 关键字来修饰该方法,这个方法就是抽象方法,用 abstract 来修饰该类就是抽象类。
比如:将 Animal 类设计成抽象类,并让子类 Cat 类实现其 eat()方法
6.3 抽象类的介绍
用 abstract 关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名 {
}用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表); //没有方法体抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
6.4 抽象类使用的注意事项和细节讨论
抽象类不能被实例化
抽象类不一定要包含 abstract 方法。也就是说,抽象类可以没有 abstract 方法
一旦类包含了 abstract 方法,则这个类必须声明为 abstract
abstract 只能修饰 类和方法,不能修饰属性和其他的
抽象类可以有任意成员【因为抽象类本质还是类】 比如:非抽象方法、构造器、静态属性等等
抽象方法不能有主体 { }
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类
抽象方法不能使用 private、final、static 来修饰,因为这些关键字都是和重写相违背的
6.5 小练习
以下代码是否能成功编译?
- abstract final class A { }
- abstract public static void test2();
- abstrcat private void test3();
- abstract public void test4() { }
编写一个 Employee 类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于 Manager 类来说,他即是员工,还具有奖金(bonus)属性,请使用继承的思想,设计CommonEmployee类和 Manager类,要求类中提供必要的方法进行属性访问,实现 work(),输出”经理/普通员工 名字 工作中…”
6.6 模板设计模式
要求:
- 有多个类,完成不同的任务 job
- 要求能够统计各自完成任务的时间
- 请编程实现
步骤:
- 先用最容易想到的方法
- 分析问题,提出使用模板设计模式
//传统解决办法
class Test {
public static void main(String[] args) {
AA aa = new AA();
aa.sum();
BB bb = new BB();
bb.res();
}
}
class A {
public void sum() {
int count = 0;
//得到开始的时间
long start = System.currentTimeMillis();
for (int i = 1; i <= 999999; i++) {
count += i;
}
//得到结束的时间
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
class B {
public void multi() {
int num = 0;
//得到开始的时间
long start = System.currentTimeMillis();
for (int i = 1; i <= 599999; i++) {
num *= i;
}
//得到结束的时间
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
/*
存在的问题:
1. A类 和 B类 存在重复的代码,
2. 如果在 A或B类中再加入相同方法,仅更改循环上限值,还要写相同的代码
3. 所以会出现代码冗余
4. 如果我们将相同的代码提取出来,写到一个新的方法内
5. 再在新的方法内调用循环体方法
6. 就能在一定程度上改善代码冗余
7. 同样的道理,如果想在多个类间实现改善
8. 可以将重复的代码提取到一个抽象类的一个方法中
9. 再将包含循环体为主体的方法做成抽象方法用以引用
10.即可达到我们的目标
*/
最佳实践:
设计一个抽象类(Template),能完成如下功能:
- 编写方法 calculateTime(),可以计算某段代码的耗时时间
- 编写抽象方法 job()
- 编写一个子类 Sub,继承抽象类Template,并实现 job方法
- 编写一个测试类TestTemplate,看看是否好用
```java
//模板设计方法
public class TestTemplate {
public static void main(String[] args) {
} } abstract class Template { public void calculateTime() {Sub sub = new Sub();
sub.calculateTime();
BB bb = new BB();
bb.calculateTime();
} public abstract void job(); } class Sub extends Template { @Override public void job() {long start = System.currentTimeMillis();
job();
long end = System.currentTimeMillis();
System.out.println(end - start);
} } class BB extends Template { @Override public void job() {int num = 0;
for (int i = 0; i < 800000; i++) {
num += i;
}
} }int num = 0;
for (int i = 0; i < 300000; i++) {
num *= i;
}
<a name="YnSVa"></a>
## 七、接口
<a name="kZWCM"></a>
### 7.1 为什么有接口
参考现实中的问题:<br />以电脑的 USB 接口为例,如果没有一个统一的规定【尺寸,形状,排线等】,usb 插槽的厂家和各个设备的厂家就没法做出完全相同的接口,比如电脑接入U盘是一种接口,电脑接入手机是一种接口,电脑接入相机是一种接口,就会造成每一种设备都要提供一个接口,势必会很混乱。<br />所以,接口的提出,可以统一规定,方便管理。
<a name="kgSmE"></a>
### 7.2 接口快速入门
这样的设计需求在Java编程/PHP/.net/go 中也是会大量存在的。
```java
package com.hspedu.interface_;
public interface UsbInterface { //接口
//规定接口的相关方法,即规范
public void start();
public void stop();
}
package com.hspedu.interface_;
//Phone 类 实现 UsbInterface
//解读 1. 即 Phone类需要实现 UsbInterface接口 规定/声明 的方法
public class Phone implements UsbInterface{
@Override
public void start() {
System.out.println("手机开始工作...");
}
@Override
public void stop() {
System.out.println("手机结束工作...");
}
}
package com.hspedu.interface_;
public class Camera implements UsbInterface{ //实现接口,把接口的方法实现
@Override
public void start() {
System.out.println("相机开始工作...");
}
@Override
public void stop() {
System.out.println("相机结束工作...");
}
}
package com.hspedu.interface_;
public class Computer {
//编写一个方法
public void work(UsbInterface usbInterface) {
usbInterface.start();
usbInterface.stop();
}
}
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); //把相机接入到计算机
}
}
7.3 基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。语法如下:
interface 接口名 {
//属性
//方法【1. 抽象方法 2. 默认方法 3. 静态方法】
}
class 类名 implements 接口 {
自己属性;
自己方法;
必须实现的接口的抽象方法
}
package com.hspedu.interface_;
public interface Interface03 {
//写属性
public int n1 = 10;
//写方法
//在接口中,抽象方法,可以省略 abstract 关键字
public void hi();
//在jdk8之后,可以有默认实现方法,需要使用default关键字修饰
default public void ok() {
System.out.println(n1);
}
//在jdk8后,可以有静态方法
public static void cry() {
System.out.println("cry...");
}
}
小结
- 在 jdk 7.0 前,接口里所有方法都没有方法体,即都是抽象方法。
- 在 jdk 8.0 后,接口类可以有静态( static )方法,默认( default )方法,也就是说接口中可以有方法的具体实现。
7.4 接口深入讨论
什么时候使用接口呢?几个应用场景:
现在要制造战斗机、武装直升机。专家只需把飞机需要的功能/规格定下来即可,然后让别的人具体实现就可
现在有一个项目经理,管理三个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。
现有3个程序员,项目经理要求编写3个类,分别完成对MySql,Oracle,DB2数据库的连接 connect,close
package com.hspedu.interface_;
//项目经理写的
public interface DBInterface {
public void connect(); //连接方法
public void close(); //关闭连接
}
package com.hspedu.interface_;
//第一个程序员写的
public class MySqlDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接数据库...");
}
@Override
public void close() {
System.out.println("关闭数据库...");
}
}
package com.hspedu.interface_;
//第二个程序员写的
public class OracleDB implements DBInterface{
@Override
public void connect() {
System.out.println("连接Oracle数据库...");
}
@Override
public void close() {
System.out.println("关闭Oracle数据库...");
}
}
package com.hspedu.interface_;
//第三个程序员写的
public class DB2DB implements DBInterface{
@Override
public void connect() {
System.out.println("连接DB2数据库...");
}
@Override
public void close() {
System.out.println("关闭DB2数据库...");
}
}
package com.hspedu.interface_;
//使用测试
public class Interface04 {
public static void main(String[] args) {
MySqlDB mySqlDB = new MySqlDB();
OracleDB oracleDB = new OracleDB();
DB2DB db2DB = new DB2DB();
t(mySqlDB);
t(oracleDB);
t(db2DB);
}
public static void t(DBInterface db) {
db.connect();
db.close();
}
}
7.5 接口的注意事项和细节
- 接口不能被实例化
- 接口中所有的方法是public 方法,所以可省略,接口中抽象方法,也可以不用 abstract 修饰
- 一个普通类实现接口,就必须将该接口的所有方法都实现,可以使用 alt+enter 快捷键
- 抽象类实现接口时,可以不用实现接口的方法
- 一个类中同时可以实现多个接口
- 接口中的属性,只能是final 的,而且是 public static final 修饰符。
比如:int a = 1; 实际上是 public static final int a = 1; - 接口中属性的访问形式:接口名.属性名
- 一个接口不能继承其他的类,但是可以继承(extends)多个别的接口
实现(implements)只能由其他类实现,接口不能实现接口 - 接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的
7.6 实现接口 VS 继承
1. 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这种方法
2. 接口比继承更加灵活
普通类的继承是单继承,满足 is - A 的关系 ,而接口只需满足 like - A 的关系,普通类和接口都可以继承多个接口
3. 接口在一定程度上实现代码解耦
即接口规范性 + 动态绑定机制
7.7 接口的多态特性
1. 动态参数
前面的 Usb 接口案例,UsbInterface usb,既可以接收手机对象,又可以接收相机对象,就体现了接口多态
2. 多态数组
演示一个案例:给 Usb 数组中,存放 Phone 和 Camera 对象,Phone类还有一个特别的方法 call(),请遍历 Usb数组,如果是 Phone 对象,除了调用 Usb接口定义的方法外,还需调用 Phone 特有方法 call
3. 接口存在多态传递现象
B接口 继承了 A接口,如果 C类 实现(implements)了 B接口,则 C类 也实现了 A接口
7.8 小节练习
- 下面代码是否存在错误?若有错请修改,并说出最后的输出结果。
interface A {
int x = 0; //相当于 public static final int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX( ) {
System.out.println(x); //错误,父类和接口都有x,没有明确说明x是来自哪一个的
}
public static void main(String[] args) {
new C().pX();
}
}
八、内部类
8.1 基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【1. 属性 2. 方法 3. 构造器 4. 代码块 5. 内部类】内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。
8.2 基本语法
class Outer { //外部类
class Inner { //内部类
}
}
class Other { //外部其他类
}
8.3 内部类的分类
1. 定义在外部类局部位置上【比如方法内】
- 局部内部类(有类名)
- 匿名内部类(没有类名) 重点 !!!
2. 定义在外部类的成员位置上
- 成员内部类(没用 static 修饰)
- 静态内部类(使用 static 修饰)
8.4 局部内部类
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名,本质仍然是一个类。
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用 final 修饰,因为局部变量也可以使用 final
- 作用域:仅仅在定义它的方法体或代码块中
- 局部内部类 — 访问 —> 外部类的成员【访问方式:直接访问】
- 外部类 — 访问 —> 局部内部类的成员
访问方式:创建内部类实例对象,再访问(注意:必须在作用域内) - 外部其他类 — 不能访问 —> 局部内部类 (局部内部类的地位就是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
8.5 匿名内部类
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
- 本质是类
- 内部类
- 该类没有名字
- 是一个对象
1. 匿名内部类的基本语法
new 类或接口(参数列表){
类体
}
2. 案例演示
package com.hspedu.innerclass;
/**
* 演示匿名内部类的使用
*/
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer04 outer04 = new Outer04();
outer04.method();
}
}
class Outer04 {
private int n1 = 10;
public void method() {
//基于接口的匿名内部类
//解读:
//1. 需求:想使用 IA 接口,并创建对象
//2. 传统方式,是写一个类,实现该接口,并创建对象
//3. 现在的需求是,Tiger 类只是使用一次,后面再不使用,所以没必要专门定义一个类
//4. 可以使用匿名内部类来简化开发
//5. tiger 的编译类型? IA
//6. tiger 的运行类型? 匿名内部类 Outer04$1
/*
我们看底层 会分配 类名 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎嗷嗷叫");
}
}
*/
//7. jdk底层在创建匿名内部类 Outer04$1 ,紧接着就创建了 Outer04$1的实例
// 并且把地址返回给 tiger
//8. 匿名内部类使用一次,就不能再使用了,因为定义在方法体/代码块中,类似于局部变量
IA tiger = new IA() {
@Override
public void cry() {
System.out.println("老虎嗷嗷叫");
}
};
System.out.println("tiger的运行类型 = " + tiger.getClass());
// Tiger tiger = new Tiger();
tiger.cry();
//基于类的匿名内部类
//分析:
//1. father 的编译类型? Father
//2. father 的运行类型? 匿名内部类 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father {
@Override
public void test() {
System.out.println("匿名内部类重写了test()方法...");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2的对象
Father father = new Father("jack") {
@Override
public void test() {
System.out.println("匿名内部类重写了test()方法...");
}
};
System.out.println("father的运行类型 = " + father.getClass());
father.test();
//基于抽象类的匿名内部类
//必须实现抽象方法
Animal animal = new Animal() {
@Override
void eat() {
System.out.println("动物吃东西");
}
};
}
}
interface IA {
void cry();
}
//class Tiger implements IA{
// @Override
// public void cry() {
// System.out.println("老虎嗷嗷叫");
// }
//}
class Father {
public Father(String name) {
}
public void test () {
}
}
abstract class Animal {
abstract void eat();
}
3. 注意事项和使用细节
- 匿名内部类的语法比较特别,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码的分析可以看出这个特点,因此可以调用匿名内部类的方法
- 可以直接访问外部类的所有成员,包括私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量
- 作用域:仅仅在定义它的方法体或代码块中
- 匿名内部类 — 访问 —> 外部类成员 【访问方式:直接访问】
- 外部其他类 —不能访问 —> 匿名内部类
- 如果外部类和内部类的成员重名时,内部类访问的 话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
4. 使用场景
当作实参直接传递,简洁高效。
package com.hspedu.innerclass;
public class InnerClassExercise {
public static void main(String[] args) {
//匿名内部类(对象)作为实参传入
f1(new AA() {
@Override
public void show() {
System.out.println("这是一个匿名内部类(对象)作为实参");
}
});
//传统方法
f1(new Something());
}
//静态方法,形参是接口类型
public static void f1(AA aa) {
aa.show();
}
}
interface AA {
void show();
}
//传统方式 - 类->实现AA接口 => 编程领域(硬编码)
class Something implements AA{
@Override
public void show() {
System.out.println("输出内容");
}
}
例题:完成以下要求
- 有一个铃声接口 Bell,里面有个 ring 方法。
- 有一个手机类 Cellphone,具有闹钟功能 alarmclock,参数是Bell类型。
- 测试手机类的闹钟功能,通过匿名内部类(对象)作为参数,打印:懒猪起床了。
- 再传入另一个匿名内部类(对象),打印:小伙伴上课了。 ```java package com.hspedu.innerclass;
public class InnerClassTest { 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(); } } ```
8.6 成员内部类
说明:成员内部类是定义在外部类的成员位置,并且没有 static 修饰
- 可以直接访问外部类的所有成员,包括私有的
- 可以添加任意访问修饰符,因为它的地位就是一个成员
- 作用域:和外部类的其他成员一样,为整个类体
- 成员内部类 — 访问 —> 外部类的成员(比如:属性)【访问方式:直接访问】
- 外部类 — 访问 —> 成员内部类 【访问方式:创建对象,再访问】
- 外部其他类 — 访问 —> 成员内部类 【两种访问方式】
- 当作外部类的成员调用
OuterClass.InnerClass inner = new OuterClass().new InnerClass(); - 在外部类中写一个返回方法,返回类型为成员内部类
public InnerClass getInnerClass() {
return new InnerClass();
}
- 当作外部类的成员调用
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
8.7 静态内部类
说明:静态内部类是定义在外部类的成员位置,并且有 static 修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符,因为它的地位就是一个成员
- 作用域:同其他的成员,为整个类体
- 静态内部类 — 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有的静态成员】
- 外部类 — 访问 —> 静态内部类 【访问方式:创建对象,再访问】
- 外部其他类 — 访问 —> 静态内部类
- 静态内部类,可以通过外部类名直接访问(前提是满足访问权限)
OuterClass.InnerClass inner = new OuterClass.InnerClass(); - 编写一个方法,可以返回静态内部类
public (static) InnerClass getInnerClass() {
return new InnerClass();
}
- 静态内部类,可以通过外部类名直接访问(前提是满足访问权限)
- 如果外部类和静态内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问 (因为静态内部类只能访问静态成员,静态成员不需要用 .this.)
学习参考(致谢):
- B站 @程序员鱼皮 Java学习一条龙
- B站 @韩顺平 零基础30天学会Java