面向对象编程:包,继承,多态,抽象类,接口
Posted 西弗勒斯斯内普
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象编程:包,继承,多态,抽象类,接口相关的知识,希望对你有一定的参考价值。
一、包
1、导入包中的类
通配符(*):
util下有很多类 Java处理的时候 需要哪个才会拿哪个
C语言 通过include关键字导入 会把这个文件的内容全部拿过来
import java.util.Date;
// import java.util; 导入一个具体的类 不能导入一个具体的包
import java.util.*; // 通配符
public class TestDemo
public static void main(String[] args)
Date data = new Date();
System.out.println(data.getTime()); // 得到一个毫秒级别的时间戳
java.util.Date date1 = new java.util.Date(); // util和sql中都存在一个Date这样的类 避免冲突
2、静态导入
使用 import static 可以导入包中的静态的方法和字段
import static java.lang.System.*;
import static java.lang.Math.*;
public class TestDemo
public static void main(String[] args)
out.println("hehe");
out.println(max(10, 20)); // out.println(Math.max(10, 20));
3、将类放到包中
在文件的最上方加上一个 package 语句指定该代码在哪个包中.
包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.bit.demo1 ).
包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
如果一个类没有 package 语句, 则该类被放到一个默认包中
包名必须是小写字母
package com.bit.demo1; // package 指定当前的类在哪个包下
public class TestDemo
同名类导入方法:
4、包的访问权限控制
成员变量不加任何修饰限定词的时候,默认就是包访问权限
同一个包 / 不同包:
此时 val 默认就是包访问权限
常见的系统包:
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net:进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包。
二、继承
面向对象的基本特征:
封装:
不必要公开的数据成员和方法,使用private关键字进行修饰。意义:安全性
继承:
对共性的抽取,使用extends关键字进行处理。意义:可以对代码进行重复使用
1、语法规则
使用 extends 指定父类
Java 中是单继承,一个子类只能继承一个父类 (而C++/Python等语言支持多继承)
子类会继承父类的所有 public 的字段和方法
对于父类的 private 的字段和方法, 子类中是无法访问的
子类构造的同时,要先帮助父类进行构造。可以使用 super 关键字得到父类实例的引用
super
-> super(); // 显示调用构造方法
-> super.func(); // 调用父类的普通方法
-> super.data; // 调用父类的成员方法属性
不能出现在静态方法中,因为super是父类对象的引用,依赖对象
class Animal
public String name;
public int age;
public void eat()
System.out.println(name+" ani::eat()");
class Dog extends Animal
class Bird extends Animal
public String wing;
public void fly()
System.out.println(name+" fly() "+age);
public class TestDemo
public static void main(String[] args)
Dog dog = new Dog();
System.out.println(dog.name); // null
dog.eat(); // null ani::eat()
Bird bird = new Bird();
System.out.println(bird.name); // null
bird.eat(); // null ani::eat()
bird.fly(); // null fly() 0
2、protected 关键字
Java 中对字段和方法共有四种访问权限:
-> private: 类内部能访问, 类外部不能访问
-> 默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
-> protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
-> public : 类内部和类的调用者都能访问
3、final 关键字
如果一个类不想被继承,可以设置final修饰
final修饰变量-> final int a = 10; // 常量 不可以被修改
final修饰类-> final class A // 代表整个类不可以被继承
final修饰方法->
我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承
三、组合
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果
例如表示一个学校:
public class Student
...
public class Teacher
...
public class School
public Student[] students;
public Teacher[] teachers;
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段
这是我们设计类的一种常用方式之一
组合表示 has - a 语义
在刚才的例子中, 我们可以理解成一个学校中 “包含” 若干学生和教师
继承表示 is - a 语义
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物
四、多态
1、向上转型
父类的引用,指向一个子类的实例,这种写法称为 向上转型
向上转型发生的时机:
-> 1. 直接赋值
-> 2. 方法传参
-> 3. 方法返回
public class TestDemo
public static void func(Animal ani)
public static Animal func2()
Dog dog = new Dog("haha", 19);
return dog;
public static void main(String[] args)
Animal animal = new Dog("haha", 19); // 父类引用 引用 子类对象
Dog dog = new Dog("haha", 19);
func(dog);
2、动态绑定,方法重写
-> 父类引用 引用 子类的对象
-> 通过这个父类引用 调用父类 和 子类同名的覆盖方法
同名的覆盖:重写(覆写/重写/覆盖(Override)):
-> 方法名相同
-> 参数列表相同(个数+类型)
-> 返回值相同(或构成协变类型也可)
-> 父子类的情况下
class Animal
public String name;
public int age;
private int count;
public Animal(String name, int age)
eat();
this.name = name;
this.age = age;
public void eat()
System.out.println(name+" ani::eat()");
class Dog extends Animal
public Dog(String name, int age)
super(name, age); // 调用父类带有2个参数的构造方法
@Override
public void eat()
System.out.println(name+" 狼吞虎咽的eat()");
class Bird extends Animal
public String wing;
public Bird(String name, int age, String wing)
super(name, age);
this.wing = wing;
public void fly()
System.out.println(super.name+" fly() "+super.age);
public class TestDemo
public static void main(String[] args)
Dog dog = new Dog("hehe", 19);
System.out.println(dog.name);
// null 狼吞虎咽的eat()
// hehe
dog.eat(); // hehe 狼吞虎咽的eat()
Bird bird = new Bird("haha", 20, "wingwing");
System.out.println(bird.name);
// null ani::eat()
// haha
bird.eat(); // haha ani::eat()
bird.fly(); // haha fly() 20
重写注意事项:
1、方法不可以是static
2、子类的访问修饰限定,要大于等于父类的访问修饰限定
3、private 方法不能 重写
4、被final 修饰的关键字,不能被重写
5、重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)-> 协变类型
6、通过父类引用 只能访问自己的成员
class Animal
public String name;
public int age;
private int count;
public Animal(String name, int age)
this.name = name;
this.age = age;
public void eat()
System.out.println(name+" eat()");
class Dog extends Animal
public Dog(String name, int age)
super(name, age); // 调用父类带有2个参数的构造方法
@Override
public void eat()
System.out.println(name+" 狼吞虎咽的eat()");
public class TestDemo
public static void main(String[] args)
Animal animal = new Dog("haha", 19); // 调用dog类带有两个参数的构造方法
animal.eat(); // haha eat()
// dog里加上eat自己的方法后运行打印:
// haha 狼吞虎咽的eat()
Java 中有两种多态:
1. 运行时多态
打开字节码文件:
编译的时候,不能确定此时到底调用的谁的方法
运行的时候才知道,调用哪个方法
运行时绑定->动态绑定
2. 编译时多态
(由重载实现,根据所给参数类型个数不同推导调用哪个函数)
class Dog extends Animal
public void func(int a)
System.out.println("int");
public void func(int a, int b)
System.out.println("int, int");
public void func(int a, int b, int c)
System.out.println("int, int, int");
public class TestDemo
public static void main(String[] args)
Dog dog = new Dog("haha", 19);
dog.func(10);
-> 重载和重写的区别:
3、向下转型
instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回 true. 这时再进行向下转型就比较安全了
public class TestDemo
public static void main(String[] args)
Animal animal = new Dog("hehe", 10);
Bird bird = (Bird)animal;
bird.fly();
Animal animal2 = new Dog("hehe", 10);
// 以免类型转换异常
if(animal2 instanceof Bird) // 判断animal2引用的是不是Bird的对象
Bird bird2 = (Bird)animal2;
bird2.fly();
在构造方法中调用重写的方法(一个坑)
在父类构造方法里调用父类和子类的重写的eat方法,也会发生动态绑定
class Animal
public String name;
public int age;
private int count;
public Animal(String name, int age)
eat();
this.name = name;
this.age = age;
public void eat()
System.out.println(name+" ani::eat()");
class Dog extends Animal
public Dog(String name, int age)
super(name, age); // 调用父类带有2个参数的构造方法
@Override
public void eat()
System.out.println(name+" 狼吞虎咽的eat()");
public class TestDemo
public static void main(String[] args)
Dog animal = new Dog("hehe", 10);
// null 狼吞虎咽的eat()
4、理解多态
shape 这个引用调用 draw 方法可能会有多种不同的表现
(和 shape 对应的实例相关),这种行为就称为 多态
使用多态的好处是什么?
1、类调用者对类的使用成本进一步降低.
封装是让类的调用者不需要知道类的实现细节.
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低
class Shape
public void draw()
System.out.println("Shape::draw()");
class Rect extends Shape
@Override
public void draw()
System.out.println("♦");
class Flower extends Shape
@Override
public void draw()
System.out.println("❀");
class Triangle extends Shape
@Override
public void draw()
System.out.println("△");
public class Test
public static void drawMap(Shape shape) // 向上转型
shape.draw(); // 动态绑定
public static void main(String[] args)
Shape shape1 = new Rect();
drawMap(shape1); // ♦
//drawMap(new Rect());
Shape shape2 = new Flower();
drawMap(shape2); // ❀
Shape shape3 = new Triangle();
drawMap(shape3); // △
2、能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
圈复杂度:
圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.
因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”. 如果一个方法的圈复杂度太高, 就需要考虑重构.
不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10
// 创建了一个 Shape 对象的数组
public static void main(String[] args)
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = triangle, rect, triangle, rect, flower;
for (Shape x : shapes)
x.draw();
// if-else 循环
public static void main1(String[] args)
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] shapes = "triangle", "rect", "triangle&以上是关于面向对象编程:包,继承,多态,抽象类,接口的主要内容,如果未能解决你的问题,请参考以下文章
Python全栈之路系列----之-----面向对象4接口与抽象,多继承与多态)