Skip to content

13. 面向对象高级特性

13.1 继承

继承(inheritance): 也称泛化,继承性是子类自动共享父类属性和方法的机制,在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入自己若干新的内容 继承简化了人们对事物的认识和描述,有益于软件复用,是OO技术提高软件开发效率的重要原因之一 是类之间的一种关系,一般类与特殊类之间的关系 继承关系的语义:“is a kind of”

在Java中定义一个类时,让该类通过关键字extends继承一个已有的类,这就是类的继承(泛化)。 被继承的类称为父类(超类,基类),新的类称为子类(派生类)。 子类继承父类的所有属性和方法,同时也可以增加自己的属性和方法。

package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Animal
 * 创建时间:  2020/11/16  10:36
 * 描述 :     动物类
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Animal {
    double weight ;
    String name;


    public void eat(){
        System.out.println("生存....");
    }

}
java
package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Panda
 * 创建时间:  2020/11/16  10:38
 * 描述 :     熊猫
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Panda extends Animal {
}

public class App {

    public static void main(String[] args) {
        Panda panda = new Panda();
        panda.eat();
    }

}

继承的规则: Java中只支持单继承,也就是说每个类只能有一个父类,不允许有多重继承 一个父类可以有多个子类 子类继承父类所有的属性和方法

在继承关系中我们还能发现一个规律:子类是父类的一种,也可以说“子类就是父类”。如:人类就是动物,动物就是生物。记住这个定律对我们理解继承的概念非常有帮助。但是,反过来看,父类是子类的说法正确吗?

13.2 子类实例化的过程

子类实例化时先实例化其父类,然后实例化子类。

要先调用父类的构造器,父类构造器运行完毕,才调用子类的构造器。 如果实例化类D,说出构造器执行的顺序。

java
package com.neusoft.extend;

public class Animal {
    double weight ;
    String name;

    static {
        System.out.println("1 Animal.static initializer");
    }
    public Animal() {
        System.out.println("4 Animal.Animal");
    }

    public void eat(){
        System.out.println("生存....");
    }

}


package com.neusoft.extend;
public class Panda extends Animal {

    static {
        System.out.println("2 Panda.static initializer");
    }
    public Panda() {
        System.out.println("5 Panda.Panda");
    }
}


package com.neusoft.extend;
public class SubPanDa extends Panda {

    static {
        System.out.println("3 SubPanDa.static initializer");
    }

    public SubPanDa() {
        System.out.println("6 SubPanDa.SubPanDa");
    }

}

13.3 调用构造方法

在构造方法中,可以使用this或super调用其他的构造方法,子类实例化的时候默认调用父类的无参构造器

  1. super()

    作用:调用父类的构造器只能出现在子类的构造器中,且必须是第一行 super()中的参数,决定了调用父类哪个构造器 如果子类构造器中没有出现super,那么编译器会默认加上super(),即调用父类的空构造器,如果父类没有空构造器,编译器提示错误。

  2. this()

    作用:调用本类的构造器 只能写在构造器的第一行

    在同一个构造器中super()和this()不能同时出现

super和this关键字

  1. super.
    • 指向父类的引用。
    • 通过关键字super我们可以指定子类在构造时调用父类的哪个构造器,达到先实例化父类然后实例化子类的目的。
    • 子类的构造器默认的调用父类无参构造器,即子类构造器中没有用super指明调用父类哪个构造器的话,实际上编译器会自动的在子类构造器第一行加入代码super( );
  2. this.
    • 指向本类的引用。
    • 我们知道子类在实例化时必须调用父类的构造器,实际上有的子类构造器也可以先调用本类的其他构造器,然后再通过那个构造器调用父类的构造器
    • 无论是调用父类的构造器还是子类的构造器,最终都是找到最顶级的父类自上而下的实例化。只要中间环节有一个构造器没找到,这个子类就无法完成实例化。
package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Animal
 * 创建时间:  2020/11/16  10:36
 * 描述 :     动物类
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Animal {
    String name;
    double weight ;



    public Animal() {
        System.out.println(" Animal.Animal");
    }

    public Animal(String name, double weight) {
        this.name = name;
        this.weight = weight;
        System.out.println("Animal.Animal name ,weight");
    }

    public void eat(){
        System.out.println("生存....");
    }

}
package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Panda
 * 创建时间:  2020/11/16  10:38
 * 描述 :     熊猫
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Panda extends Animal {

    double height;

    public Panda() {
        this(10.0);
        System.out.println(" Panda.Panda");
    }

    public Panda(double height) {
        super("贝贝",100.0);
        this.height = height;
        System.out.println("Panda.Panda height");
    }

}


public class App {

    public static void main(String[] args) {

        Panda panda = new Panda();

    }

}

13.4 封装

生活中的封装

  1. ​ 信息隐藏,隐藏对象的实现细节,不让用户看到 . ​ 将东西包装在一起,然后以新的完整形式呈现出来 ​ 例如,两种或多种化学药品组成一个胶囊

Java中封装的应用

  1. ​ 隐藏类的实现细节; . ​ 让使用者只能通过事 一起包装到一个单元中,单元以类的形式实现; . ​ 便于修改,增强代码的可维护性; . ​ 可进行数据检查。

13.4.1 权限修饰符

使用访问权限修饰符对类的成员进行控制,在Java中称为“封装”。 用来控制类的成员和类的使用范围

privatedefaultprotectedpublic
同一类
同一包中的类
不同包中的子类
其他包中的类

按照权限从大到小排列为: public > protected > default > private

不要把封装理解为private,不要误认为不能访问成员才是封装。实际上对成员访问权限的任何控制(包括public)都称为封装机制。 封装的常用设置:

​ 构造器和类的权限通常为public;

  1. ​ private权限最小,限制类外访问,一般把属性设为private,让其他类不能直接访问属性,达到保护属性的目的; . ​ 不使用权限修饰符时(即default)的成员在类内以及在同一个包中的其他类可以访问; . ​ protected所修饰的成员在类内、同一个包中、所在类的子类中都可以访问。

13.4.2 protected权限修饰符

​ protected:受保护的,除了可被类自身及与其在同一个包中的代码访问外,还可以在一个类中通过对象引用来访问,前提是这些对象引用至少应该具有与该成员所在的类相同的类型,也即是这些引用具有该成员所在类的类型或是其一子类型。

13.5 方法的覆盖

方法的覆盖(override) :

还可以称为重写(rewrite),是对从父类中继承来的方法进行改造,只有在子类继承父类时发生。

  1. 方法覆盖的规则 在子类中的覆盖方法与父类中被覆盖的方法应具有
    1. 相同的方法名
    2. 相同的参数列表(参数数量、参数类型、参数顺序都要相同)
    3. 相同的返回值类型
    4. 子类覆盖方法的访问权限要不小于父类中被覆盖方法的访问权限
  2. 在重写方法上,可以使用@Override注解来标明是重写方法 @Override表示重写(也可以省略):编译器验证@Override下面的方法名是否是父类中所有的,如果没有则报错。
java
package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Animal
 * 创建时间:  2020/11/16  10:36
 * 描述 :     动物类
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Animal {
    String name;
    double weight ;



    public Animal() {
        System.out.println(" Animal.Animal");
    }

    public Animal(String name, double weight) {
        this.name = name;
        this.weight = weight;
        System.out.println("Animal.Animal name ,weight");
    }

    public void eat(){
        System.out.println("生存....");
    }

}
java
package com.neusoft.extend;

/**
 * 项目:      javaadvice
 * 类名:       Tiger
 * 创建时间:  2020/11/16  13:51
 * 描述 :
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Tiger extends Animal {


    /**
     * @Override 表示覆盖父类中的方法
     */
    @Override
    public void eat(){
        System.out.println("吃肉....");
    }
}

13.6 abstract和final

在Java中使用abstract关键字定义抽象方法 抽象类声明格式

例如:

[访问权限修饰符]  abstract   返回值类型    抽象方法名 (参数列表) ;

抽象方法

只有方法声明,没有方法实现的方法

抽象方法需要子类重写该方法,因此不能用private、final修饰

java
public abstract class Animal {
    String name;
    double weight ;



    //抽象类
    public  abstract void eat();

    public  void play(){
        System.out.println("玩耍");
    }

}

抽象类是抽象方法和非抽象方法的集合,包含特殊情况如下:

抽象类中可以全部是抽象方法

抽象类中可以全部为非抽象方法

抽象类的规则

  1. 注意:

    • 抽象类不能被实例化;

    • 包含的抽象方法必须在其子类中被实现,否则子类只能声明为abstract;

    • 抽象方法不能为static;

  2. 在下列情况下,一个类必须声明为抽象类:

    • 当一个类的一个或多个方法是抽象方法时;
    • 当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分;
    • 当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;

final关键字

final可以修饰的元素:

类:不能被继承,代表类String

java
//如下代码报错
public class Child extends  String {
}

变量(属性和局部变量):不能被重新赋值

java
public class Father {
    public final String name  ; //声明的时候必须给初值

    public Father() {
        name = "xxxx";
    }

    public static void main(String[] args) {
        System.out.println(new Father().name);
        Father f = new Father();
//        f.name ="aaaaa";
        
    }

}

在声明时赋值,或在构造器中赋值 系统不会对final属性默认的赋初始值 方法:不能在子类中被覆盖,即不能修改。

java
public class Child extends  Father {

    //父类的finla方法子类不能重写
//    public void sayHello(){
//        System.out.println("child hello");
//    }


    public static void main(String[] args) {
        Father father = new Child();
    }
}

13.7 接口

Java继承时一个类只有一个直接父类,也就是单继承,但是一个类可以实现多个接口,接口弥补了类的不能多继承缺点,继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。

接口的概念

  1. ​ 接口中只包含常量和抽象方法,而没有变量和方法的实现 . ​ 接口对类来说是一套规范,是一套行为协议; . ​ 接口不是一个类,不能实例化

语法格式

  1. ​ 接口的成员:
    1. 常量(字段)
    2. 抽象方法
public interface El {
    public abstract  void work();
}

13.7.1 接口和接口的继承

13.8. 引用数据类型的转换(多态)

java
        El el = new Wash();  //真实的对象是 Wash

        Computer c = (Computer)el;
        c.play();

instanceof 运算符, 判断对象是否是引用类型

通过instanceof来判断该经过上溯转型后是哪一个子类的。 经过上溯和下溯造型之后,我们很难知道某个引用到底指向哪种类型的对象。 使用instanceof运算符的一般格式: 判断一个实例对象是否属于一个类 object instanceof class 判断一个类是否实现了某个接口 object instanceof interface 返回值都是boolean类型

if(el instanceof  Computer) {
Computer c = (Computer) el;
c.play();
}
System.out.println(el instanceof  Wash); // true
System.out.println(el instanceof  El); //true

13.8 多态

多态(动态绑定、Polymorphism ) 不同的对象对同一行为作出的不同响应,多态存在的三个必要条件 要有继承,或实现 要有重写 父类引用指向子类对象 一旦满足以上3个条件,当调用父类中被重写的方法后,运行时创建的是哪个子类的对象,就调用该子类中重写的那个方法 在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际类型调用相应的方法

多态的优点 简化代码 改善代码的组织性和可读性 易于扩展

13.9 内部类

内部类特性 内部类就是定义在另一个类内部的类。 内部类对于同一包中的其它类来说,内部类能够隐藏起来。 语法:

java
[访问权限修饰符]  class  类名{                          
  [访问权限修饰符]  class  类名{                          
      内部类成员
  }
  外部类成员
}
  • 注意:
  1. 内部类可以访问其外部类中所有的属性和方法,无需创建外部类的对象。
  2. 必须创建内部类的对象,否则无法从外部类访问内部类的变量和方法。
  3. 如果内部类中有和外部类同名的变量或方法,则内部类的变量和方法将获得比外部类的变量和方法更高的优先级。
  4. 不能定义static变量

13.9.1 普通内部类

一个类只会被这个类所调用,其他类不会使用他,你可以把它定义成一内部类,这样可以隐藏实现细节,避免错误的调用。

java
package com.neusoft.innerclass;

/**
 * 项目:      javaadvice
 * 类名:       Outer
 * 创建时间:  2020/11/17  8:45
 * 描述 :
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Outer {


    private String name;


    public  void sayHello(){
        System.out.println("name:"+this.name);
    }


    class Inner{

        private Integer age;

        public void innerHi(){
            System.out.println(Outer.this.name);
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


}

13.9.2 静态内部类

用static标识的内部类为静态内部类。

静态内部类特点:

  1. 静态内部类作为外部类的静态成员,不能访问外部类非静态成员。
  2. 非静态内部类只能定义非静态成员,而静态内部类可以定义静态成员和非静态成员。

静态内部类实例化:

  1. 使用Outer.Inner inn=new Outer.Inner()方式实例化静态内部类。
  2. 非静态内部类不可以使用上面的方式实例化。

定义静态内部类语法:

java
[访问权限修饰符]  class  类名{                          
  [访问权限修饰符]  static  class  类名{                          
      内部类成员
  }
  外部类成员
}
java
package com.neusoft.innerclass;

/**
 * 项目:      javaadvice
 * 类名:       Outer
 * 创建时间:  2020/11/17  8:45
 * 描述 :
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Outer {


    private static Double PI;

    public static void sayHello1(){
//        System.out.println("name:"+this.name);
    }


    static class Inner2{
        public static String id ="2020年11月17日08:57:53";
        public static void sayHello1(){
//            System.out.println("name:"+Outer.this.name);
        }
    }

}

13.9.3 局部内部类

  1. 局部内部类概念: 在一个类的方法体中或程序块内定义的内部类
  2. 局部内部类特点: 在方法定义的内部类中只能访问方法中的final类型的局部变量
public class Outer2 {
      public int a = 1;
      private int b = 2;
      public void method( final int c) {
          int d = 3;
          final int e = 2;
          class Inner {
              private void iMethod( int e) {
                  //System.out.println(e);
              }
          }
      }
  }

13.9.4 匿名内部类

没有定义类名的内部类,称为匿名内部类 匿名内部类特点:

  1. 匿名内部类没有访问修饰符。
  2. new 匿名内部类,这个类首先是要存在父类或接口。
  3. 外部类方法形参或局部变量需要被匿名内部类使用,必须为final。

语法

java
new 父类|接口(){    
     //重写父类或接口的抽象方法    
}
java
package com.neusoft.innerclass;

import java.util.function.Consumer;

/**
 * 项目:      javaadvice
 * 类名:       Out
 * 创建时间:  2020/11/17  9:03
 * 描述 :
 * 作者 :     张金山 
 * QQ:	314649444
 * Site:      https://jshand.gitee.io
 */
public class Out {

    private String name;

    public void sayHello(){

        final int num = 100;


        //局部内部类,匿名内部类
        new Consumer<String>(){

            public void accept(String s) {
                System.out.println("name:"+s);

                System.out.println(num);
            }
        }.accept("张飞");

        System.out.println(num);
//        num++ ; // 此处不能再修改

    }

    

    public static void main(String[] args) {
        new Out().sayHello();
    }

}

13.10. lambda 表达式

在JDK8以后版本,引入lambda表达式,作用:

  1. 当做是一个匿名方法使用;
  2. 允许我们将行为传到函数里。

在Java 8之前,如果想将行为传入函数,仅有的选择是使用匿名内部类。

语法包含三部分:

  1. 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
  2. 一个箭头符号:->
  3. 方法体,可以是表达式和代码块。

语法:

(参数) -> { 方法体 }

需求:建立一套数学运算的软件(两个数)

/*
* 计算两个数的数学运算
*/
public interface MathOpreation {
    void operation(int numa,int numb);
}

使用子类型实现加减法

java
public class PlusOperation implements MathOpreation {
    @Override
    public void operation(int numa, int numb) {
        System.out.println("加法:"+(numa +numb));
    }
}
java
public class SubOperation implements MathOpreation {
    @Override
    public void operation(int numa, int numb) {
        System.out.println("减法:"+(numa -numb));
    }
}

调用过程

java
public class App {
    public static void main(String[] args) {

        int numa = 10;
        int numb = 20;

//        MathOpreation mathOpreation = new PlusOperation();
        MathOpreation mathOpreation = new SubOperation();
        mathOpreation.operation(numa,numb);

    }
}

因为运算过程只调用一次,而且变换的频率比较快,替换成局部内部类实现

java
public class App {
    public static void main(String[] args) {

        int numa = 10;
        int numb = 20;


        new MathOpreation(){

            @Override
            public void operation(int numa, int numb) {
                System.out.println("乘法:"+(numa*numb));
            }
        }.operation(numa,numb);
        
    }
}

上述实现过程,可以使用lambda表达式

java
public class App {
    public static void main(String[] args) {

        int numa = 10;
        int numb = 20;


//        new MathOpreation(){
//
//            @Override
//            public void operation(int numa, int numb) {
//                System.out.println("乘法:"+(numa*numb));
//            }
//        }.operation(numa,numb);


        //lambda 为了替换接口函数 ,
        MathOpreation mo = (int a, int b)->{
            System.out.println("除法:"+(a/b));
        };

        mo.operation(numa, numb);
    }

}

要求接口必须是函数是接口(有且仅有一个抽象方法),可以在类上声明 @FunctionalInterface用于检查

java
@FunctionalInterface
public interface MathOpreation {
    void operation(int numa,int numb);
}

13.10.1 lambda表达式的几种写法

Function 有参数,有返回值

java
Function<String,String> f =  s ->{
	String result = "hello:" +s;
	return result;
};

String  res = f.apply("张飞");

Consumer 消费者

java
Consumer<String> c = (String s) ->{
	System.out.println(s);
};

c.accept("hello");

Predicate 断言

java
Predicate<Integer>  p = (num) ->{
   return !(num>100);
};

System.out.println(p.test(50));

Supplier 提供者

java
Supplier<String> s = () ->{
       return "name";
};

System.out.println(s.get());

带参数 (参数类型 变量) ->

        Consumer<String> c = (String s) ->{
            System.out.println(s);
        };
        c.accept("hello");

//类型推导可以不用声明变量类型 (变量) ->

Consumer<String> c = ( s) ->{
      System.out.println(s);
};
c.accept("hello");

参数类型只有一个可以去掉效果好 变量 ->

java
Consumer<String> c =  s ->{
      System.out.println(s);
};
c.accept("hello");

2 没有参数 () ->

java
 Supplier<String> s = () ->{
            return "name";
        };
s.get();

3 有返回值 ([参数类型] 变量 ) ->{ reutrn 返回值 }

Predicate<Integer>  p = (num) -> {
   return !(num > 100);
};

4 如果方法只有一句话,可以不用{} ,最后一个表达式最为return的返回值

java
 Predicate<Integer>  p = (num) ->   !(num > 100);

13.10.2 方法的引用

方法引用 跟 lambda 表达式一样的

语法:ObjectReference::methodName

方法引用使得开发者可以直接引用静态方法、构造方法或者实例对象

构造器引用,语法是Class::new、Class<T>::new

静态方法引用,语法是Class::static_method

类的成员方法的引用,语法是Class::method

实例对象的成员方法的引用,语法是instance::method

Released under the MIT License.