06-面向对象编程(下)

06—面向对象编程(下)

1. 关键字:static

​ 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象, 其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少 对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。

image-20210530224606173
  • static:静态的
  • static可以用来修饰:属性、方法、代码块、内部类
1.1 static关键字的修饰属性
  • 使用static修饰属性:静态变量(类变量)

    • 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)

      • 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
      • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
      • image-20210628163357103
    • static修饰属性的其他说明:

      • 静态变量随着类的加载而加载。可以通过“类.静态变量”的方式进行调用 如:System.out

      • 静态变量的加载要早于对象的创建。

      • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。

        • 类变量 实例变量
          类.类变量 ×
          对象 对象.类变量 对象.实例变量
    • 静态属性举例:System.out ; Math.PI ;

  • image-20210628165519723
1.2 static关键字的修饰方法
  • 使用static修饰方法:静态方法

    • 随着类的加载而加载,可以通过“类.静态方法”的方式进行调用

      • 静态方法 非静态方法
        类.静态方法 ×
        对象 对象.静态方法 对象.非静态方法
    • 静态方法中,只能调用静态的方法或属性,非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性

      image-20210710133850210

    • static注意点:

      • 在静态方法内,不能使用this关键字、super关键字
      • 关于静态属性和静态方法的使用,从生命周期的角度去理解(晚出生的可以调用早出生的,早出生的不能调晚出生的)
    • 开发中,如何确定一个属性是否要声明未static

      ​ 属性是可以被多个对象所共享的,不回随着对象的不同而不同

      开发中,如何确定一个方法是否要声明为static

      ​ 操作静态属性的方法,通常设置为static的

      ​ 工具类中的方法,习惯上声明为static。比如Math、Arrays、Collections

注意

​ 静态方法可以被继承,但是,不能被覆盖,即重写。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可以使用语法:父类名.静态方法调用隐藏的静态方法。

​ 如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性

​ 因此,通过一个指向子类对象的父类引用变量来调用父子同名的静态方法时,只会调用父类的静态方法。

1.3 单例模式

设计模式

在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己思考和摸索。

单例设计模式

采取一定的方法,保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//单例饿汉式
public class SingletonTest1{
public static void main(String[] args){
//此时的bank1与bank2指向的是同一个对象
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);//true
}
}

class Bank{
//1.私有化类的构造器
private Bank(){

}

//2.内部创建类的对象,此对象也必须为静态的,否则静态方法里无法对其进行返回
private static Bank instance = new Bank();

//3.提供公共的静态方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//单例懒汉式
public class SingletonTest2{
public static void main(String[] args){
Order Order1 = Order.getInstance();
Order Order2 = Order.getInstance();
System.out.println(Order1 == Order2);//true
}
}

class Order{
//1.私有化类的构造器
private Order(){

}

//2.声明对象,并不进行初始化,否则静态方法里无法对其进行初始化
private static Order instance = null;

//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}

饿汉式 vs 懒汉式

  • 饿汉式:

    • 坏处:对象加载时间过长
    • 好处:饿汉式是线程安全的
  • 懒汉式:

    • 好处:延迟对象创建
    • 坏处:目前写法是不安全的
      • 例如,一个线程在调用getInstance()时,判断完if,准备新建对象,另一个线程也同样进行到if里,最后导致新建出两个对象。
  • 单例模式的优点::

    • 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的 产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  • 举例 java.lang.Runtime

    image-20210815205450550

应用场景

  1. 网站的计数器,一般也是单例模式实现,否则难以同步。
  2. 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志 文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  3. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
  4. 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
  5. Application 也是单例的典型应用
  6. Windows的Task Manager (任务管理器)就是很典型的单例模式
  7. Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

2. 理解main方法的语法

​ 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是 public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须 是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。

​ 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。

2.1 命令行参数用法举例
1
2
3
4
5
6
7
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}

//运行程序CommandPara.java java CommandPara “Tom” “Jerry”

image-20210815205937678

输出结果:

args[0] = Tom
args[1] = Jerry

面试题

此处,Something类的文件名叫OtherThing.java

1
2
3
4
5
class Something {
public static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}

上述程序是否可以正常编译、运行?

image-20210815210742392

3. 类的成员之四:代码块

  • 代码块(或初始化块)的作用:

    • 对Java类或对象进行初始化
  • 代码块(或初始化块)的分类:

    • 一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块 (static block),没有使用static修饰的,为非静态代码块。
  • static代码块通常用于初始化static的属性

    1
    2
    3
    4
    5
    6
    7
    8
    class Person {
    public static int total;
    static {
    total = 100;//为total赋初值
    }
    …… //其它属性或方法声明
    }

3.1 静态代码块
1
2
3
4
//static的代码块
static{
System.out.println("hello,static block");
}

内部可以有输出语句,随着类的加载而执行,而且只执行一次,如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。静态代码块的执行优先于非静态代码块的执行。静态代码块内只能调用静态的方法、静态的属性,不能调用非静态的结构

作用:初始化类的信息

3.2 非静态代码块
1
2
3
{
System.out.println("hello,block");
}

内部可以有输出语句,随着对象的创建而执行,每创建一个对象,就执行一次非静态的代码块.如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。

作用:可以在创建对象时,对对象的属性等进行初始化

对属性可以赋值的位置以及先后顺序:

①默认初始化,②显式初始化;在代码块中赋值,③构造器中初始化,④有了对象以后,可以通过“对象.属性”或”对象.方法”的方式进行赋值

3.3 代码块执行顺序测试

LeafTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
System.out.println();
new Leaf();
}
}

输出结果:

Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器

Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器

因为是继承关系,所以首先会加载Father的静态代码块,然后加载Son的静态代码块,之后在加载Father的构造方法,然后在加载Son的构造方法,且静态代码块在这个类被调用时只执行一次。

总结:对于继承的方法来讲,首先会加载父类,而静态代码块是在类加载时加载的,所以对于新建子类对象时,会先从最高一级的父类静态代码块开始加载。然后再从最高一级的父类非静态代码块和无参构造器逐级加载(因为对于没显式写有this()的方法,会隐式的调用super() )。

Son.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}

public Father() {
System.out.println("33333333333");

}

}

public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}


public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("|++++++++++++++++++++++|");
new Son();
System.out.println("|++++++++++++++++++++++|");
new Son();
System.out.println("|++++++++++++++++++++++|");
new Father();
}

}

11111111111
44444444444
77777777777
|++++++++++++++++++++++|
22222222222
33333333333
55555555555
66666666666
|++++++++++++++++++++++|
22222222222
33333333333
55555555555
66666666666
|++++++++++++++++++++++|
22222222222
33333333333

先执行静态代码块,且在类加载时执行,并且只执行一次,若有多个静态代码块,则会按照静态代码块在代码中的顺序来执行静态代码块。

  再执行普通(构造代码块)代码块,先于构造方法执行,可以执行多次,构造方法执行几次,构造代码块就执行几次

  (优先级从高到低)静态代码块 > main方法 > 构造代码块 > 构造方法。

4. 关键字:final

final修饰变量

final修饰的变量究竟是怎么个不变性呢

答案就是:对于final修饰的变量来说,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的,也就是说在对其初始化之后便不能再让其指向另一个对象。

4.1 final修饰的实例变量
  1. 被final修饰的实例变量必须显示的指定初始值
  2. 对于普通实例变量,Java程序可以对它执行默认的初始化,也就是将实例变量的值指定为默认的初始值0或null,但对于final修饰的实例变量,则必须由程序员显示的赋予初始值。
  3. final实例变量必须显示地被赋初始值,而且本质上final实例变量只能在构造器中被赋初始值。在定义final实例变量时指定初始值,和在初始化块中为final实例变量指定初始值本质上是一样的。除此之外,final实例变量将不能被再次赋值。

方法一:直接赋值

1
2
3
4
public class FinalTest {
final int f1 = 0;
final String f2 = "you";
}

方法二:构造器中赋值

1
2
3
4
5
6
7
8
public class FinalTest {
final int f1;
final String f2;
public FinalTest(int f1,String f2){
this.f1 =f1;
this.f2 = f2;
}
}
4.2 final修饰的类变量

对于final类变量而言,同样必须显示指定初始值,而且final类变量只能在2个地方指定初始值:

  1. 定义final类变量时指定初始值;

    1
    2
    3
    4
    5
    6
    public calss FinalTest{
    final Test test = new Test();
    }
    class Test{

    }
  2. 在静态初始化块中为final类变量指定初始值;

    1
    2
    3
    4
    5
    6
    7
    8
    public calss FinalTest{
    static{
    final Test test = new Test();
    }
    }
    class Test{

    }

    这两种方式都会被抽取到静态初始化块中赋初始值。定义final类变量时指定初始值和在静态初始化块中为final类变量指定初始值,本质是一样的。除此之外final类变量将不能被再次赋值。

4.3 final修饰局部变量

final修饰的局部变量一样需要被显式地赋初始值,因为Java本来就要求局部变量必须被显式地赋初始值。与普通变量不同的是,final修饰的局部变量被赋初始值之后,将不能再被重新赋值。

1
2
3
4
5
6
7
public class FinalTest {
public void foo(final int num){
final int number = num;
final int number2;
number2 = 10;
}
}
4.4 内部类中的局部变量
  1. 这里不仅仅是匿名内部类,即使是普通内部类,在任何内部类中访问的局部变量都应该使用final修饰。

  2. 此处说的内部类指的是局部内部类,只有局部内部类(包括匿名内部类)才可以访问局部变量,普通静态内部类、非静态内部类不可能访问方法体内的局部变量。

  3. Java要求所有被内部类访问的局部变量都使用final修饰,对于普通局部变量而言,它的作用域就是停留在该方法内,当方法执行结束,该局部变量也随之消失。但内部类则可能产生隐式的“闭包”闭包将使得局部变量脱离它所在的方法继续存在。JDK8之前,匿名内部类访问的局部变量必须要用final修饰

  4. 匿名内部类的实例生命周期没有结束的话,将一直可以访问局部变量的值,这就是内部类会扩大局部变量作用域的实例。

  5. 由于内部类可能扩大局部变量的作用域,如果再加上这个被内部类访问的局部变量没有使用final修饰,也就是说该变量的值可以随意改变,就会引起大乱。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class FinalTest {
    int num = 10;
    class Test{//成员内部类
    public void show(){
    num = 100;
    System.out.println(num);
    }
    }

    public void method(){
    int m1;//省略了final
    int[] m2 = new int[1];//省略了final
    class TestInner{//局部内部类
    public void display(){
    // m1 = 10;//Variable 'm1' is accessed from within inner class, needs to be final or effectively final
    m2[0] = 100;//指向的对象没有变,但是对象里的内容可以更改
    m2[0] = 1000;//指向的对象没有变,但是对象里的内容可以更改
    }
    }
    }
    }

因此Java编译器要求所有被内部类访问的局部变量必须使用final修饰

总而言之:

  1. final修饰符的第一简单的功能就是一旦被赋初始值,将不可改变。

  2. final的另一个简单的功能就是在定义了该final类变量时指定了初始值,且该初始值可以在编译时就被确定下来,系统将不会在静态初始化块中对该类变量赋初始值,而将是在类定义中直接使用该初始化值代替该final变量。

  3. 对于一个使用final修饰的变量而言,如果定义该final变量时就指定初始值,而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其变成“宏变量”处理。所有出现该变量的地方,系统将直接把它当成对应的值处理。

5. 抽象类与抽象方法

5.1 抽象类与抽象方法的概念

​ 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一 般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。

image-20210815215240015

  • 用abstract关键字来修饰一个类,这个类叫做抽象类。

  • 用abstract来修饰一个方法,该方法叫做抽象方法。

    • 抽象方法:只有方法的声明,没有方法的实现。以分号结束:

      比如:public abstract void talk();

  • 含有抽象方法的类必须被声明为抽象类。

  • 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。

  • 不能用abstract修饰变量、代码块、构造器;

  • 不能用abstract修饰私有方法、静态方法、final的方法、final的类。

5.2 抽象类与抽象方法的使用

abstract关键字的使用

1.abstract可以用来修饰的结构:类、方法

2.abstract修饰类:抽象类

  • 此类不能实例化
  • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
  • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作

3.abstract修饰方法:抽象方法

  • 抽象方法只有方法的声明,没有方法体
  • 包含抽象方法的类,一定是个抽象类。反之抽象类不一定包含抽象方法
  • 若子类重写了父类中的所有抽象方法后,此子类方可实例化,若子类没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要用abstract修饰。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class AbstractTest{
public static void main(String[] args){
//一旦Person类抽象了,就不可以实例化
// Person p1 = new Person();
// p1.walk();
}
}

abstract class Person{
String name;
int age;

public Person(){
}

public Person(String name,int age){
this.name = name;
this.age = age;
}

//抽象方法
public void eat();

public void walk(){
System.out.println("人走路");
}
}

class Student extends Person{
public Student(String name,int age){
super(name,age);
}

public void eat(){
System.out.println("学生应该多吃有营养的食物。")
}
}
5.3 多态的应用:模板方法设计模式

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
  • 换句话说,在软件开发中实现一个算法时,整体步骤很容易固定、通用,这些步骤在父类写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。

使用模板方法计算某个抽象方法执行所需时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class TemplateTest{
public static void main(String[] args) {
SubTemplate subTemplate = new SubTemplate();
subTemplate.spendTime();
}

}

abstract class Template{
//计算某段代码执行所需花费的时间
public void spendTime() {
long start = System.currentTimeMillis();
this.code();//不确定的部分,易变的部分
long end = System.currentTimeMillis();
System.out.println("花费时间是:"+(end - start));
}

public abstract void code();
}

class SubTemplate extends Template{

@Override
public void code() {
// TODO Auto-generated method stub
for(int i = 2; i < 1000; i++) {
boolean isFlag = true ;
for(int j = 2; j <= Math.sqrt(i); j++ ) {
if(i % j == 0) {
isFlag = false;
break;
}
}
if(isFlag) {
System.out.println(i);
}
}
}

}

6. 接口(interface)

6.1 接口概述
  • 一方面,有时候必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
  • 另一方面,有时必须从几个类中抽取一些共同的特征行为,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机……都支持USB连接。
  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要……则必须能……”的思想,继承是一个“是不是”的关系,而接口实现的则是“能不能”的关系
  • 接口的本质是契约,标准,规范
image-20210703195856881
6.2 接口的使用
  1. 接口使用interface来定义

  2. Java中,接口和类是并列的两个结构

  3. 如何定义接口:定义接口中的成员

    1. JDK7及以前:只能定义全局常量和抽象方法
      1. 全局常量:public static final的,但是书写时可以省略不写。
      2. 抽象方法:public abstract的,但是书写时可以省略不写
      3. image-20210703202800208
    2. JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
  4. 接口中不能定义构造器,此举意味着接口不可以实例化。

  5. Java开发中,接口通过类去实现(implements)的方式来使用。

    1. 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
    2. 如果实现类没有覆盖接口中的所有抽象方法,则此实现类仍为一个抽象类
  6. Java类可以实现多个接口—>弥补了Java的多继承

    1. 格式class AA extends BB implements CC,DD,EE{

      ​ }

  7. 接口与接口之间可以继承,而且可以多继承

  8. 接口的具体使用,体现多态性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    public class USBTest {
    public static void main(String[] args) {
    Computer computer = new Computer();
    //方式一:创建接口的非匿名实现类的非匿名对象
    Flash flash = new Flash();
    computer.transferData(flash);
    System.out.println("********************************");
    //方式二:创建接口的非匿名实现类的匿名对象
    computer.transferData(new Flash());
    System.out.println("********************************");
    //方式三:创建接口的匿名实现类的非匿名对象
    USB phone = new USB(){

    @Override
    public void start() {
    System.out.println("手机接入,开始工作");
    }

    @Override
    public void stop() {
    System.out.println("手机结束传输");
    }
    };
    computer.transferData(phone);
    System.out.println("********************************");
    //方式四:创建接口的匿名实现类的匿名对象
    computer.transferData(new USB(){

    @Override
    public void start() {
    System.out.println("未知设备接入,开始工作");
    }

    @Override
    public void stop() {
    System.out.println("未知设备传输完成");
    }
    });
    }
    }

    interface USB{
    void start();
    void stop();
    }

    class Flash implements USB{

    @Override
    public void start() {
    System.out.println("Flash接入设备,传输准备");
    }

    @Override
    public void stop() {
    System.out.println("Flash传输结束");
    }
    }

    class Computer{
    public void transferData(USB usb){
    usb.start();
    System.out.println("具体传输细节");
    usb.stop();
    }
    }
    image-20210703213322669
  9. 接口,实际上可以看作一种规范,在开发中体现面向接口编程

6.3 接口应用:代理模式 & 工厂模式

代理模式

代理模式是Java开发中使用较多的一种设计模式。代理模式设计就是为其他对象提供一种代理,以控制这个对象的访问。

imgimg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);

proxyServer.browse();
}
}

interface NetWork{
public void browse();
}

//被代理类
class Server implements NetWork{

@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}

//代理类
class ProxyServer implements NetWork{
private NetWork netWork;

public ProxyServer(NetWork netWork) {
this.netWork = netWork;
}

public void check(){
System.out.println("启用网络服务前的检查工作");
}
@Override
public void browse() {
check();
netWork.browse();
}
}

应用场景

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法的调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对现象

比如要开发一个大文档查看软件,大文档中有大得图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用Proxy来进行大图片的打开

  • 分类
    • 静态代理(静态定义代理类)
    • 动态代理(动态生成代理类)
      • JDK自带的动态代理类,需要反射等知识
6.4 接口笔试题

[面试题]排错

题目1.接口与类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX(){
// System.out.println(x); 出现错误,不明确的x
System.out.println(super.x);//1
System.out.println(A.x);//0 接口中的都为static,可以通过“接口名.属性"进行调用
}
public static void main(String[] args){
new C().pX();
}
}

知识点:如果一个类需要调用父类中重名的属性,则可以使用”super.属性”进行调用,同时应该注意,不存在”super.super…”的用法,换言之,子类只能调用直接父类中的属性,而不能调用父类的父类的属性。(在开发中,尽量不要写重名的属性)

如果接口的实现类,要调用接口中的属性,可以使用”接口.属性”的方式进行调用,因为接口中的属性全部为static final

题目2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Playable {
void play();
}

interface Bounceable {
void play();
}

interface Rollable extends Playable,Bounceable {
Ball ball = new Ball("PingPang");
}

class Ball implements Rollable {
private String name;

public String getName(){
return name;
}

public Ball(String name){
this.name = name;
}

public void play(){
// ball = new Ball("Football"); 接口中的ball是final的,无法对其进行改变
System.out.println(ball.getName());
}
}
6.5 抽象类与接口的问题
  1. abstract 能修饰哪些结构? 修饰以后,有什么特点?

    类、方法。
    类不能实例化,提供子类
    抽象方法,只定义了一种功能的标准。具体的执行,需要子类去实现。

  2. 接口是否能继承接口? 抽象类是否能实现(implements)接口? 抽象类是否能继承非抽象的类?

    能,能,能

  3. 声明抽象类,并包含抽象方法。测试类中创建一个继承抽象类的匿名子类的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //方式一:
    abstract AA{
    public abstract void m();
    }

    main(){
    AA a = new AA(){
    public void m(){}
    };
    a.m();
    }

    //方式二:

    class Person{
    String name;
    public void eat(){}
    }

    main(){
    Person p = new Person(){
    public void eat(){}
    };
    p.eat();
    }
  4. 抽象类和接口有哪些共同点和区别?

    相同点:不能实例化,都可以被继承
    不同点:抽象类:有构造器。 接口:不能声明构造器
    多继承vs 单继承

7. 类的成员之五:内部类

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部类的完整结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类

  • 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。

  • Inner class一般用在定义它的类或语句块之内,在外部引用它时,必须给出完整的名称

    • Inner class的名字不能与包含它的外部类类名相同;
  • 分类:成员内部类(static成员内部类和非static成员内部类)

    ​ 局部内部类(不谈修饰符)、匿名内部类

7.1 成员内部类
  • 成员内部类作为类的成员的角色:

    • 和外部类不同,Inner class还可以声明为private或protected;
    • 可以调用外部类的结构
    • Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
  • 成员内部类作为类的角色:

    • 可以在内部定义属性、方法、构造器等结构
    • 可以声明为abstract类 ,因此可以被其它的内部类继承
    • 可以声明为final的
    • 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
  • 注意

    • 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
    • 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
    • 成员内部类可以直接使用外部类的所有成员,包括私有的数据
    • 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
  • 举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Outer {
    private int s;
    public class Inner {
    public void mb() {
    s = 100;
    System.out.println("在内部类Inner中s=" + s);
    }
    }
    public void ma() {
    Inner i = new Inner();
    i.mb();
    }
    }
    public class InnerTest {
    public static void main(String args[]) {
    Outer o = new Outer();
    o.ma();
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Outer {
    private int s = 111;
    public class Inner {
    private int s = 222;
    public void mb(int s) {
    System.out.println(s); // 局部变量s
    System.out.println(this.s); // 内部类对象的属性s
    System.out.println(Outer.this.s); // 外部类对象属性s
    }
    }
    public static void main(String args[]) {
    Outer a = new Outer();
    Outer.Inner b = a.new Inner();
    b.mb(333);
    }
    }

7.2 局部内部类
  • 如何声明局部内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class 外部类{
    方法(){
    class 局部内部类{}
    }
    {
    class 局部内部类{}
    }
    }

  • 如何使用局部内部类

    • 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
    • 但是它的对象可以通过外部方法的返 回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
  • 局部内部类的特点

    • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
    • 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
    • 局部内部类可以使用外部类的成员,包括私有的。
    • 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
    • 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
    • 局部内部类不能使用static修饰,因此也不能包含静态成员
7.3 匿名内部类

​ 匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。

  • 格式:

    new 父类构造器(实参列表)| 实现接口(){

    ​ //匿名内部类的类体部分

    }

  • 匿名内部类的特点

    • 匿名内部类必须继承父类或实现接口
    • 匿名内部类只能有一个对象
    • 匿名内部类对象只能使用多态形式引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
7.4 内部类的使用

成员内部类和局部内部类,在编译以后,都会生成字节码文件。

格式:成员内部类:外部类$内部类名.class

​ 局部内部类:外部类$数字 局部内部类名.class (数字用来表示重名的局部内部类中的第几个 (因为为局部内部类,类名只在局部有效,所以可以定义多个相同的名字,数字只是用来做区分))

image-20210705225524602

image-20210705180138002

InnerClassTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package 内部类;
/*
类的内部成员之五:内部类
1.Java中允许将一个类A声明在另一个类B里,则类A就是内部类,类B称为外部类
2.内部类分类:成员内部类(静态、非静态) vs 局部内部类
成员内部类:定义在类里面,方法外面,构造器外面,代码块外面
局部内部类:方法内,代码块内,构造器内
3.成员内部类
一方面作为外部类的成员
调用外部内的结构
可以被static修饰
可以被4种不同的权限修饰符修饰
另一方面,作为一个类:
类内部可以定义属性、方法、构造器等
可以被final修饰,表示此类不能被继承。不使用final就可以被继承
可以被abstract修饰
4.关注如下的3个问题
4.1 如何实例化成员内部类的对象
4.2 如何在成员内部类中区分调节外部类的结构
4.3 开发中局部内部类的使用
*/
public class InnerClassTest {
public static void main(String[] args) {
//创建Dog实例(静态成员内部类)
Person.Dog dog = new Person.Dog();
dog.show();

//创建Bird实例(非静态的成员内部类)
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
bird.display("小鸟");
}
}

class Person{
String name = "小明";
int age;

public void eat(){
System.out.println("人:吃饭");
}

//静态成员内部类
static class Dog{
String name = "小狗";
int age;

public void show(){
System.out.println("卡拉是条狗");
// eat();静态方法只能调用静态方法
}
}

//非静态成员内部类
class Cat{
String name = "小猫";
public Cat(){

}
public void show(){
System.out.println("小猫抓老鼠");
}
}

class Bird{
String name = "杜鹃";
public Bird(){

}

public void sing(){
System.out.println("我是一只小小鸟");
eat();//完整写法应该为“Person.this.eat();"
}

public void display(String name){
System.out.println("在Bird类里调用形参name:name:"+ name);
System.out.println("在Bird类里调用Bird类里面的name属性:this.name:"+this.name);
System.out.println("在Bird类里调用外部类Person的name属性:Person.this.name:"+Person.this.name);
System.out.println("在Bird类里调用外部类Person的静态Dog类里的name属性:new Dog.name:"+new Dog().name);
System.out.println("在Bird类里调用外部类Person的非静态Cat类里的name属性:new Cat().name:"+new Cat().name);
}
}

public void method(){
//局部内部类
class AA{

}
}

{
//局部内部类
class BB{

}
}
public Person(){
//局部内部类
class CC{

}
}
}

卡拉是条狗
我是一只小小鸟
人:吃饭
在Bird类里调用形参name:name:小鸟
在Bird类里调用Bird类里面的name属性:this.name:杜鹃
在Bird类里调用外部类Person的name属性:Person.this.name:小明
在Bird类里调用外部类Person的静态Dog类里的name属性:new Dog.name:小狗
在Bird类里调用外部类Person的非静态Cat类里的name属性:new Cat().name:小猫

InnerClassTest1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package 内部类;

public class InnerClassTest1 {
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一
class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();//返回一个实现了接口的有名类的匿名对象

//方式二
/*
return new Comparable() { //返回一个实现了接口的匿名类的匿名对象
@Override
public int compareTo(Object o) {
return 0;
}
}
*/
}
}

7.5 内部类使用注意事项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.atguigu.java;
public class InnerClassTest {
// public void onCreate(){
// int number = 10;
// View.OnClickListern listener = new View.OnClickListener(){ // public void onClick(){
// System.out.println("hello!");
// System.out.println(number);
// }
// }
// button.setOnClickListener(listener);
// }
/*
* 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话,
* 要求此局部变量声明为final的。
* jdk 7及之前版本:要求此局部变量显式的声明为final的
* jdk 8及之后的版本:可以省略final的声明
*
*/
public void method(){
//局部变量
int num = 10;
class AA{
public void show(){
// num = 20;
System.out.println(num);
}
}
}
}