| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> java面向对象编程_包_继承_多态_重载和重写_抽象类_接口_this和super -> 正文阅读 |
|
[Java知识库]java面向对象编程_包_继承_多态_重载和重写_抽象类_接口_this和super |
目录点击跳转包
无法在一个包下创建相同类名的类! 语法格式 包的命名方法我们 1)、名称只能由字母、数字、下划线、$符号组成 2)、不能以数字开头 3)、名称不能使用JAVA中的关键字。 4)、坚决不允许出现中文及拼音命名。 导入包中的类
我们已经知道了, 系统包的介绍我们
可以看到我们就接触了 注意事项
传送门点击跳转继承顾名思义
运行结果 继承语法
我们知道
我们可以看到
这就是继承简单的作用!继承实现了代码重写的效果! 基础知识我们所继承的父类,在
父类中的private修饰的私有属性和方法无法被继承
构造方法当我们子类有构造方法时,要先帮助父类进行构造! 基础语法
我们知道编译器会自动帮助我们编写不带参数的构造方法!
如果我们不帮父类构造编译器就会报错
|
NO | 范围 | public | protected | default | privated |
---|---|---|---|---|---|
1 | 不同包中的非子类 | ?? | |||
2 | 不同包中的子类 | ?? | ?? | ||
3 | 同一包中的不同类 | ?? | ?? | ?? | |
4 | 同一包中的同一类 | ?? | ?? | ?? | ?? |
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。多态(百度词条)
子类对象赋值给了父类引用
该对象只能访问父类的字段和方法!
子类对象赋值给了父类引用
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_1 {
public static void main(String[] args) {
Animal animal = new Animal();
animal = new Dog(); //1.子类对象赋值给了父类引用
Animal animal1 = new Dog(); //2.和 1 等价
animal.eat(); //调用父类中的方法
//error animal只能访问父类中的字段和方法!
animal.height=1;
animal.running();
}
}
//方法传参
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
func(dog);
func(new Dog()); //子类对象赋值给了父类引用!
}
}
//方法返回
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static Animal func(){
return new Dog(); //子类对对象返回给了父类引用!
}
public static void main(String[] args) {
Animal animal = func();
animal.eat();
}
}
动态绑定就是,当子类重写了父类的方法时,向上转型后,对象调用与重写的方法,访问的是子类对象中的重写方法!
//运行时绑定
class Animal{
protected String name;
protected int age;
public void eat(){
System.out.println("animal eat()!");
}
}
class Dog extends Animal{
protected int height;
@Override
public void eat() {
System.out.println("dog eat()!");
}
public void running(){
System.out.println("dog running()!");
}
}
public class Test_2 {
public static void main(String[] args) {
Animal animal = new Dog();
animal.eat(); //运行时绑定!
}
}
为啥要叫运行时绑定呢?
难道说编译的时候没有绑定?
确实如此,当我们查看java
的反汇编代码时就会发现,编译期间anmial
调用的是自己的eat
方法,但是运行时却绑定了子类的eat()
!
java
反汇编代码步骤:javap -c 类名
代码 回车即可!我们想一想多态可以帮助我们做些什么!
bug郭的理解
eg:打印图形
//类的实现者
class Shape {
public void draw() {
// 啥都不用干
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("?");
}
}
//类的调用者
public class Test_1{
public static void main(String[] args) {
Shape shape1 = new Flower();
Shape shape2 = new Cycle();
Shape shape3 = new Rect();
drawShape(shape1);
drawShape(shape2);
drawShape(shape3);
}
// 打印单个图形
public static void drawShape(Shape shape) {
shape.draw();
}
}
使用多态的好处是什么?
封装是让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。
if - else
。我们知道向上转型是子类对象赋值给了父类引用!
那向下转型莫不就是:父类对象赋值给了子类引用~
并不常见~了解一下即可!
//向下转型
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println("我是一只小动物");
System.out.println(this.name + "正在吃" + food);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void eat(String food) {
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
public void fly() {
System.out.println(this.name + "正在飞");
}
}
public class Test_2 {
public static void main(String[] args) {
Animal animal = new Bird("鸽鸽"); //先借助向上转型
animal.eat("脐橙");
//animal.fly; //error
Bird bird;
bird = (Bird)animal; //向下转型需要强转
bird.fly();
}
}
可以看到向下转型步骤比较繁琐,通常要借助向上转型!
而且我们需要确定是否为父子类关系!避免异常!
利用
instanceof
关键字可以判定一个引用是否是某个类的实例。
若真返回true
,若假返回false
!
//坑
class B {
public B() {
// do nothing
func();
}
public void func() {
System.out.println("B.func()");
}
}
class D extends B {
private int num = 1;
@Override
public void func() {
System.out.println("D.func() " + num);
}
}
public class Test_3{
public static void main(String[] args) {
D d = new D();
}
}
bug郭
看了半天愣是没整明白为啥这个代码运行结果是这样!!!
我的理解:创建子类对象d会调用自己的构造方法,子类要先帮助父类构造,而父类中调用了子类重写的方法,动态绑定了;
我们并有执行子类中的 private int num = 1;
语句!所以num
此时并没有赋值!所以为0
!
正解
D
对象的同时, 会调用B
的构造方法.B
的构造方法中调用了func
方法, 此时会触发动态绑定, 会调用到D
中的func
D
对象自身还没有构造, 此时num
处在未初始化的状态, 值为 0
.结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏但又及难发现的问题!
在刚才的打印图形例子中, 我们发现, 父类Shape
中的 draw
方法好像并没有什么实际工作, 主要的绘制图形都是由Shape
的各种子类的 draw
方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod)
, 包含抽象方法的类我们称为 抽象类(abstract class)
。
abstract class Shape {
abstract public void draw();
}
在 draw
方法前加上abstract
关键字, 表示这是一个抽象方法。同时抽象方法没有方法体(没有{ }
, 不能执行具体代码)。
对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类。
注意事项
abstract class Shape {
protected int longth;
protected int wide;
public int area(){
return longth*wide;
}
abstract public void draw();
}
子类继承父类抽象类,必须重写父类中的抽象方法!
抽象方法不能是 private
的
我们知道private
修饰只能在当前类中访问,而抽象方法需要子类实现!
抽象类存在的最大意义就是为了被继承。
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题!
//定义接口类型
interface IAnimal{
//抽象方法
// public static final 字段
}
使用interface
定义一个接口
接口中的方法一定是抽象方法, 因此可以省略 abstract
接口中的方法一定是public
, 因此可以省略public
接口中只能包含抽象方法,对于字段来说, 接口中只能包含静态常量(final static)
interface IAnimal{
// public static final 字段 可以省略public static final
String name = "animal"; //因为是final 修饰,需要赋初值!
int age = 18;
//抽象方法
public abstract void speak();
void eat(); //省略public abstract
}
子类 使用 implements
继承接口。 此时表达的含义不再是 “扩展”, 而是 “实现”
在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例。
接口不能单独被实例化。
扩展(extends)
vs 实现(implements)
扩展指的是当前已经有一定的功能了, 进一步扩充功能。
实现指的是当前啥都没有, 需要从头构造出来。
接口弥补了java
无法多继承的缺陷!
我们可以实现多个接口
基本语法
有时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的。然而 Java 中只支持单继承, 一个类只
extends
一个父类。 但是可以同时实现多个接口, 也能达到多继承类似的效果。
class 类名 implement interfa1,interface2,....interfaceN{
//实现所有接口中的方法!
}
实例一
下面我们通过类来表示张三
//实现多个接口
interface IAnimal{
String name = "animal";
int age = 18;
void speak();
void eat();
}
interface IPeople{
String iQ = "140";
void study();
}
class Zhansan implements IAnimal,IPeople{
@Override
public void speak() {
System.out.println("Speak Chinese!");
}
@Override
public void eat() {
System.out.println("Eat food!");
}
@Override
public void study() {
System.out.println("Study java!");
}
}
public class Test_6 {
public static void main(String[] args) {
Zhansan zhansan = new Zhansan();
zhansan.eat();
zhansan.speak();
zhansan.study();
}
}
实例二
现在我们通过一个类来表示一组动物。
class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
}
另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”。
interface IFlying {
void fly();
}
interface IRunning {
void run();
}
interface ISwimming {
void swim();
}
接下来我们创建几个具体的动物
猫, 是会跑的。
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在用四条腿跑");
}
}
鱼, 是会游的。
class Fish extends Animal implements ISwimming {
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name + "正在用尾巴游泳");
}
}
青蛙, 既能跑, 又能游(两栖动物)
class Frog extends Animal implements IRunning, ISwimming {
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在往前跳");
}
@Override
public void swim() {
System.out.println(this.name + "正在蹬腿游泳");
}
}
提示,
IDEA
中使用ctrl + i
快速实现接口
还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”
class Duck extends Animal implements IRunning, ISwimming, IFlying {
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在用翅膀飞");
}
@Override
public void run() {
System.out.println(this.name + "正在用两条腿跑");
}
@Override
public void swim() {
System.out.println(this.name + "正在漂在水上");
}
}
上面的代码展示了 Java
面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
继承表达的含义是
is - a
语义, 而接口表达的含义是 具有xxx
特性 。
猫是一种动物, 具有会跑的特性。
青蛙也是一种动物, 既能跑, 也能游泳
鸭子也是一种动物, 既能跑, 也能游, 还能飞
这样设计有什么好处呢?
时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力!
例如, 现在实现一个方法, 叫 “散步”
public static void walk(IRunning running) {
System.out.println("我带着伙伴去散步");
running.run();
}
在这个walk
方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的, 就行
Cat cat = new Cat("小猫");
walk(cat);
Frog frog = new Frog("小青蛙");
walk(frog);
甚至参数可以不是 “动物”, 只要会跑
class Robot implements IRunning {
private String name;
public Robot(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + "正在用轮子跑");
}
}
Robot robot = new Robot("机器人");
walk(robot);
我们java
系统包中的很多类都实现了很多接口,使得该类具有某种属性
给对象排序!
//创建student类
class Student{
private String name;
private double score;
public Student(String name,double score){
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public static void main(String[] args) {
//学生对象数组
Student[] students = new Student[]{
new Student("张三",88.7),
new Student("李四",67),
new Student("王五",98),
};
Arrays.sort(students); //排序
System.out.println(students);
}
显然,我们无法排序对象类型的数据,
报异常,说我们Student
类没有实现Comparable
接口!
Comparable
接口中的抽象方法 compareTo
方法详细信息
int compareTo(T o)
将此对象与指定的对象进行比较以进行排序。 返回一个负整数,零或正整数,因为该对象小于,等于或大于指定对象。
实现程序必须确保sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
所有x
和y
。 (这意味着x.compareTo(y)
必须抛出异常if y.compareTo(x)
引发异常。)
实施者还必须确保关系是可传递的: (x.compareTo(y)>0 && y.compareTo(z)>0)表示x.compareTo(z)>0
。
最后,实施者必须确保x.compareTo(y)==0意味着sgn(x.compareTo(z)) == sgn(y.compareTo(z))
,对于所有z
。
强烈建议,但不要严格要求(x.compareTo(y)==0) == (x.equals(y))
。 一般来说,任何实现Comparable
接口并违反这种情况的类应清楚地表明这一点。 推荐的语言是“注意:此类具有与equals
不一致的自然排序”。
在前面的描述中,符号sgn( )
表达式表示数学符号函数,其定义根据表达式的值是否为负,零或正返回的-1
一个,0
,或1
。
参数
o -
要比较的对象。
结果
负整数,零或正整数,因为该对象小于,等于或大于指定对象。
异常
NullPointerException
- 如果指定的对象为空
ClassCastException
- 如果指定的对象的类型阻止它与该对象进行比较。
看到这么多文字,是不是头都大了!
没有关系,bug郭
也头大,不过我知道咋用
//创建student类并且实现Comparable接口
class Student implements Comparable{
private String name;
private double score;
public Student(String name,double score){
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
@Override //实现compareTo方法
public int compareTo(Object o) {
Student s = (Student)o; //对o强转, o指的是待比较对象
if (this.score > s.score) { //当前对象的score值大
return -1;
} else if (this.score< s.score) { //对象o的score值大
return 1;
} else {
return 0;
}
}
}
我们可以看到,这时排的升序,如果要降序就只需将大于号变成小于号即可!
在 sort
方法中会自动调用compareTo
方法. compareTo
的参数是 Object
, 其实传入的就是 Student
类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回0;
注意事项: 对于 sort
方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备compareTo
这样的能力. 通过重写compareTo
方法的方式, 就可以定义比较规则。
为了进一步加深对接口的理解, 我们可以尝试自己实现一个sort
方法来完成刚才的排序过程(使用冒泡排序)!
public static void sort(Comparable[] array) {
for (int bound = 0; bound < array.length; bound++) {
for (int cur = array.length - 1; cur > bound; cur--) {
if (array[cur - 1].compareTo(array[cur]) > 0) {
// 说明顺序不符合要求, 交换两个变量的位置
Comparable tmp = array[cur - 1];
array[cur - 1] = array[cur];
array[cur] = tmp;
}
}
}
}
接口可以继承一个接口, 达到复用的效果. 使用
extends
关键字!
interface IAmphibious extends IRunning, ISwimming {
}
class Frog implements IAmphibious {
@Override
public void run() {
System.out.println("Frog run!");
}
@Override
public void swim() {
System.out.println("Frog swim!");
}
}
public class Test_1 {
public static void main(String[] args) {
Frog frog = new Frog();
frog.run();
frog.swim();
}
}
通过接口继承创建一个新的接口
IAmphibious
表示 “两栖的”. 此时实现接口创建的Frog
类, 就继续要实现run
方法,也需要实现swim
方法!
接口间的继承相当于将多个接口合并在一起!
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 5:04:56- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |