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("生存....");
}
}
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,说出构造器执行的顺序。
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调用其他的构造方法,子类实例化的时候默认调用父类的无参构造器
super()
作用:调用父类的构造器只能出现在子类的构造器中,且必须是第一行 super()中的参数,决定了调用父类哪个构造器 如果子类构造器中没有出现super,那么编译器会默认加上super(),即调用父类的空构造器,如果父类没有空构造器,编译器提示错误。
this()
作用:调用本类的构造器 只能写在构造器的第一行
在同一个构造器中super()和this()不能同时出现
super和this关键字
- super.
- 指向父类的引用。
- 通过关键字super我们可以指定子类在构造时调用父类的哪个构造器,达到先实例化父类然后实例化子类的目的。
- 子类的构造器默认的调用父类无参构造器,即子类构造器中没有用super指明调用父类哪个构造器的话,实际上编译器会自动的在子类构造器第一行加入代码super( );
- 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 封装
生活中的封装
- 信息隐藏,隐藏对象的实现细节,不让用户看到 . 将东西包装在一起,然后以新的完整形式呈现出来 例如,两种或多种化学药品组成一个胶囊
Java中封装的应用
- 隐藏类的实现细节; . 让使用者只能通过事 一起包装到一个单元中,单元以类的形式实现; . 便于修改,增强代码的可维护性; . 可进行数据检查。
13.4.1 权限修饰符
使用访问权限修饰符对类的成员进行控制,在Java中称为“封装”。 用来控制类的成员和类的使用范围
private | default | protected | public | |
---|---|---|---|---|
同一类 | √ | √ | √ | √ |
同一包中的类 | √ | √ | √ | |
不同包中的子类 | √ | √ | ||
其他包中的类 | √ |
按照权限从大到小排列为: public > protected > default > private
不要把封装理解为private,不要误认为不能访问成员才是封装。实际上对成员访问权限的任何控制(包括public)都称为封装机制。 封装的常用设置:
构造器和类的权限通常为public;
- private权限最小,限制类外访问,一般把属性设为private,让其他类不能直接访问属性,达到保护属性的目的; . 不使用权限修饰符时(即default)的成员在类内以及在同一个包中的其他类可以访问; . protected所修饰的成员在类内、同一个包中、所在类的子类中都可以访问。
13.4.2 protected权限修饰符
protected:受保护的,除了可被类自身及与其在同一个包中的代码访问外,还可以在一个类中通过对象引用来访问,前提是这些对象引用至少应该具有与该成员所在的类相同的类型,也即是这些引用具有该成员所在类的类型或是其一子类型。
13.5 方法的覆盖
方法的覆盖(override) :
还可以称为重写(rewrite),是对从父类中继承来的方法进行改造,只有在子类继承父类时发生。
- 方法覆盖的规则 在子类中的覆盖方法与父类中被覆盖的方法应具有
- 相同的方法名
- 相同的参数列表(参数数量、参数类型、参数顺序都要相同)
- 相同的返回值类型
- 子类覆盖方法的访问权限要不小于父类中被覆盖方法的访问权限
- 在重写方法上,可以使用@Override注解来标明是重写方法 @Override表示重写(也可以省略):编译器验证@Override下面的方法名是否是父类中所有的,如果没有则报错。
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
* 类名: 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修饰
public abstract class Animal {
String name;
double weight ;
//抽象类
public abstract void eat();
public void play(){
System.out.println("玩耍");
}
}
抽象类是抽象方法和非抽象方法的集合,包含特殊情况如下:
抽象类中可以全部是抽象方法
抽象类中可以全部为非抽象方法
抽象类的规则
注意:
抽象类不能被实例化;
包含的抽象方法必须在其子类中被实现,否则子类只能声明为abstract;
抽象方法不能为static;
在下列情况下,一个类必须声明为抽象类:
- 当一个类的一个或多个方法是抽象方法时;
- 当类是一个抽象类的子类,并且没有实现父类的所有抽象方法,即只实现部分;
- 当一个类实现一个接口,并且不能为全部抽象方法都提供实现时;
final关键字
final可以修饰的元素:
类:不能被继承,代表类String
//如下代码报错
public class Child extends String {
}
变量(属性和局部变量):不能被重新赋值
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属性默认的赋初始值 方法:不能在子类中被覆盖,即不能修改。
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继承时一个类只有一个直接父类,也就是单继承,但是一个类可以实现多个接口,接口弥补了类的不能多继承缺点,继承和接口的双重设计既保持了类的数据安全也变相实现了多继承。
接口的概念
- 接口中只包含常量和抽象方法,而没有变量和方法的实现 . 接口对类来说是一套规范,是一套行为协议; . 接口不是一个类,不能实例化
语法格式
- 接口的成员:
- 常量(字段)
- 抽象方法
public interface El {
public abstract void work();
}
13.7.1 接口和接口的继承
13.8. 引用数据类型的转换(多态)
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 内部类
内部类特性 内部类就是定义在另一个类内部的类。 内部类对于同一包中的其它类来说,内部类能够隐藏起来。 语法:
[访问权限修饰符] class 类名{
[访问权限修饰符] class 类名{
内部类成员
}
外部类成员
}
- 注意:
- 内部类可以访问其外部类中所有的属性和方法,无需创建外部类的对象。
- 必须创建内部类的对象,否则无法从外部类访问内部类的变量和方法。
- 如果内部类中有和外部类同名的变量或方法,则内部类的变量和方法将获得比外部类的变量和方法更高的优先级。
- 不能定义static变量
13.9.1 普通内部类
一个类只会被这个类所调用,其他类不会使用他,你可以把它定义成一内部类,这样可以隐藏实现细节,避免错误的调用。
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标识的内部类为静态内部类。
静态内部类特点:
- 静态内部类作为外部类的静态成员,不能访问外部类非静态成员。
- 非静态内部类只能定义非静态成员,而静态内部类可以定义静态成员和非静态成员。
静态内部类实例化:
- 使用Outer.Inner inn=new Outer.Inner()方式实例化静态内部类。
- 非静态内部类不可以使用上面的方式实例化。
定义静态内部类语法:
[访问权限修饰符] class 类名{
[访问权限修饰符] static class 类名{
内部类成员
}
外部类成员
}
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 局部内部类
- 局部内部类概念: 在一个类的方法体中或程序块内定义的内部类
- 局部内部类特点: 在方法定义的内部类中只能访问方法中的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 匿名内部类
没有定义类名的内部类,称为匿名内部类 匿名内部类特点:
- 匿名内部类没有访问修饰符。
- new 匿名内部类,这个类首先是要存在父类或接口。
- 外部类方法形参或局部变量需要被匿名内部类使用,必须为final。
语法
new 父类|接口(){
//重写父类或接口的抽象方法
}
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表达式,作用:
- 当做是一个匿名方法使用;
- 允许我们将行为传到函数里。
在Java 8之前,如果想将行为传入函数,仅有的选择是使用匿名内部类。
语法包含三部分:
- 一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数
- 一个箭头符号:->
- 方法体,可以是表达式和代码块。
语法:
(参数) -> { 方法体 }
需求:建立一套数学运算的软件(两个数)
/*
* 计算两个数的数学运算
*/
public interface MathOpreation {
void operation(int numa,int numb);
}
使用子类型实现加减法
public class PlusOperation implements MathOpreation {
@Override
public void operation(int numa, int numb) {
System.out.println("加法:"+(numa +numb));
}
}
public class SubOperation implements MathOpreation {
@Override
public void operation(int numa, int numb) {
System.out.println("减法:"+(numa -numb));
}
}
调用过程
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);
}
}
因为运算过程只调用一次,而且变换的频率比较快,替换成局部内部类实现
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表达式
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用于检查
@FunctionalInterface
public interface MathOpreation {
void operation(int numa,int numb);
}
13.10.1 lambda表达式的几种写法
Function 有参数,有返回值
Function<String,String> f = s ->{
String result = "hello:" +s;
return result;
};
String res = f.apply("张飞");
Consumer 消费者
Consumer<String> c = (String s) ->{
System.out.println(s);
};
c.accept("hello");
Predicate 断言
Predicate<Integer> p = (num) ->{
return !(num>100);
};
System.out.println(p.test(50));
Supplier 提供者
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");
参数类型只有一个可以去掉效果好 变量 ->
Consumer<String> c = s ->{
System.out.println(s);
};
c.accept("hello");
2 没有参数 () ->
Supplier<String> s = () ->{
return "name";
};
s.get();
3 有返回值 ([参数类型] 变量 ) ->{ reutrn 返回值 }
Predicate<Integer> p = (num) -> {
return !(num > 100);
};
4 如果方法只有一句话,可以不用{} ,最后一个表达式最为return的返回值
Predicate<Integer> p = (num) -> !(num > 100);
13.10.2 方法的引用
方法引用 跟 lambda 表达式一样的
语法:
ObjectReference::methodName
方法引用使得开发者可以直接引用静态方法、构造方法或者实例对象
构造器引用,语法是
Class::new、Class<T>::new
静态方法引用,语法是
Class::static_method
类的成员方法的引用,语法是
Class::method
实例对象的成员方法的引用,语法是
instance::method
