提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、包
    • 1.1导入包中的类
    • 1.2静态导入
    • 1.3将类放到包中
    • 1.4包的访问权限控制
    • 1.5常见的系统包
  • 二、继承
    • 2.1背景
    • 2.2语法规则
    • 2.3protected关键字
    • 2.4更复杂的继承关系
    • 2.5final关键字
  • 三、组合
  • 四、多态
    • 4.1向上转型(子类对象给父类)
    • 4.2动态绑定
    • 4.3方法重写
    • 4.4理解多态
    • 4.5向下转型(父类对象给子类)
    • 4.6super关键字
    • 4.7在构造方法中调用重写的方法
  • 五、抽象类
    • 5.1语法规则
    • 5.2抽象类的作用
  • 六、接口
    • 6.1语法规则
    • 6.2实现多个接口
    • 6.3接口的继承
    • 6.4Clonable接口和深拷贝
    • 6.6小结

前言

提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、包

包(package)是组织类的一种方式,使用包的目的是保证类的唯一性:
大白话讲:你代码中有一个Person类,你同学也写了一个Person类,如果将来你们代码需要一起用就会出现同名类而报错。

1.1导入包中的类

比如我们现在要得到一个时间戳:

import java.util.Date;
public class TestDemo {public static void main(String[] args) {Date data=new Date();//通过import java.util.Date;实现//如果你不想导入java.util这个包中的Date类,你可以按如下写法实现相同的效果java.util.Date date=new java.util.Date();//效果和上面的一样,但写法比较麻烦System.out.println(data.getTime());}
}

ps:import:“引入”,指引入类中需要的类
package:“包”,指类所在的包

我们需要用到java类库里的一些代码时,我们需要用import进行相应的导入,需要注意的是,我们import是导入一个具体的类而不能是导入一个具体的包,比如上面的得到时间戳

import java.util.Date;//正确
import java.util;//错误

关于import.util.*

*是一个通配符,它会导入一个包里面所有的类,那这个时候就有问题了:util下面有很多的类,那你import.util. *就一下子全部导入了嘛?

答案是否定的:java处理的时候,需要哪个类才会拿哪个类,这里要区别C语言的include<>,C中导入某个头文件就会把那个头文件的所有内容全部拿过来,java相对更细化一点。

比如上面写的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());}
}

我们只用了util里面Date的类,那么*也只会导入Date的类,而不是一股脑全拉过来。

我们实际工作或学习中还是建议导入具体的类,那个import java.xxx. *;范围还是太大了,有时候你把握不住。 举个栗子:

import java.util.*;
import java.sql.*;
public class TestDemo {public static void main(String[] args) {Date data=new Date();}
}

你可能当时是想用util里面的Data,但是后面有些程序又需要用sql里面的一些类,你一股脑util和sql都是用*来导入,没有导入具体的类,问题就出现了。

util和sql里面都有Data这个类,那电脑怎么知道你要导入哪个包里面的Date啊,这里就会报错。

1.2静态导入

静态导入,工作学习都使用的很少,大家了解即可。
使用import static可以导入包中的静态方法和字段。

举例说明:我们平时经常会用到System.out.println();System是一个类,我们通过import static java.lang.System.*;导入这个类,然后这个类底下的静态方法就可以直接使用了,我们直接out.println()即可完成打印

import static java.lang.System.*;
public class TestDemo {public static void main(String[] args) {out.println("hello");}
}

静态导入写出来的程序对理解程度要求高,且看起来稀奇古怪,我们了解即可,不需要深入理解。

1.3将类放到包中

基本规则:
1.在文件的最上方加一个package语句,指定该代码在哪个包中。
2.包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(比如:你原先域名为www.bidu.com那么颠倒形式就是com.bidu.www)
3.包名要和代码路径相匹配,例如创建com.bit.demo1的包,那么会存在一个对应的路径com/bit/demo1来存储代码
4.如果一个类没有package语句,则将该类放到一个默认包中。

步骤如下:
1.在IDEA中先新建一个包,右键src->new>package

2.在弹出的对话框中输入包名,比如com.bit.demo1

3.在包中创建类,右键包名->新建->类,然后输入类名即可

4.可以看到我们的磁盘上的目录结构已经被IDEA自动创建了

5.同时我们也可以看到,在新建的test.java文件上方,出现了package语句

1.4包的访问权限控制

我们已经了解了类中的public和private。private里的成员只能被类的内部使用。如果,某个成员不含public和private关键字,那么这个成员可以在包内部的其他雷使用,但是不能在包外部的类使用
示例如下:
我们在一个包com.bit.demo1中创建了2个类:Demo1和Demo2

在Demo1中不加任何访问修饰符创建int val=1;

那么相同包下面的Demo2也可以用这个val

打印结果为1:

但是出了这个包,我们再来测试这段相同的代码

这里就会报错。

1.5常见的系统包

1.java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入
2.java.lang.reflect:java反射编程包
3.java.net:进行网络编程开发包
4.java.sql:进行数据库开发的支持包
5.java.util:java提供的工具程序包,集合类等,非常重要
6.java.io:I/O编程开发包

二、继承

2.1背景

代码中创建的类,主要是为了抽象现实中的一些事物(包括属性和方法)
有时候客观事物之间就存在一些关联关系,那么在表示成类和对象的时候,也会存在一定的关联。
代码示例如下:

class Dog{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}
}
class Bird{public String name;public int age;public String wing;//翅膀public void eat(){System.out.println(name+"正在吃东西");}public void fly(){System.out.println(name+"正在飞行");}
}

我们现在写了两个类:Dog和Bird,它们作为动物有一些相同的属性和方法,比如name,age,eat(),那么我们可以把这些共性都抽象出来,放在一个Animal类里面 ,然后我们让Dog和Bird分别继承Animal类,即可达到代码重用的效果。

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}
}

Animal这样被继承的类,我们称为父类、基类、或超类,对于像Dog和Bird这样的类,我们称为子类、派生类,也很好理解嘛,你父亲买的电脑,你也可以用它来打电动嘛哈哈哈。

2.2语法规则

class 子类 extends 父类{}

1.使用extends指定父类
2.java中一个子类只能继承一个父类(C++或其他一些语言支持多继承)
3.子类会继承父类的所有public的字段和方法
4.对于父类的private的字段和方法,子类中是无法访问的
5.子类的实例中,也包含着父类的实例,可以用super关键字得到父类实例的引用
举例说明:(我们仍用上面的Dog和Bird举例)

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}
}
class Dog extends Animal{//原先相同的代码就可以不用写了
}
class Bird extends Animal{public String wing;//翅膀public void fly(){System.out.println("正在飞行");}
}
public class Demo1{public static void main(String[] args) {Dog d=new Dog();d.name="xiaoHei";System.out.println(d.name);d.eat();}
}

运行效果如下:

我们用子类创建一个对象后,它的在内存里是这样的:

我们子类继承父类后,我们new一个子类的对象,会在堆上创建父类有的东西和子类有的东西。比如我们上面的代码,狗是动物的子类,狗这个类里面本没有东西,但继承了动物里的name,age,那么堆里面就会开辟一块内存放name和age。Bird这个类里面有wing,再继承动物这个类里面的name和age,那么堆里面就会开辟一块空间放name、age、wing

当我们在父类中加入构造方法,原先的代码就会报错了,为什么呢?

子类构造的同时,需要先帮助父类进行构造,也就是子类里面要调用父类的构造方法,这里我是这样理解的:子类要继承一些东西,你父类得有那些东西,就像是你想继承你爸爸的餐厅啥的,你爸爸得有餐厅。而优先帮父类构造也就是我们下面要说的super

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}public Animal(String name,int age){this.name=name;this.age=age;}
}
class Dog extends Animal{public Dog(String name,int age){super(name,age);//super:显示调用构造方法// 这里的super(name,age)就是调用父类的两个参数构造方法
}
}
class Bird extends Animal{public Bird(String name,int age,String wing){//这里传参个数不受父类构造方法影响,你需要几个参数你就传几个参数super(name,age);this.wing=wing;}public String wing;//翅膀public void fly(){System.out.println("正在飞行");}
}

我们在Dog和Bird里面都弄一个构造方法来调用父类的构造方法,
super(实际参数列表…)表示显示调用父类中的构造方法,它用来表示当前对象的父类型特征,就比如这里Dog这个类里面的super(name,age)就是表示狗子的名字和年龄,名字和年龄是父类动物的特征。

这个时候可能会有同学会问,那我们之前没有构造方法的时候为什么没有报错?在笔者上一篇java类和对象文章中,我们说过,一个类没有任何构造方法时,系统会自动生成一个无参的构造方法
那么我们这里子类父类会这样无参生成构造方法:

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}public Animal(){}
}
class Dog extends Animal{public Dog(){super();
}

所以在你改变父类的构造方法后,子类原先和父类对应的无参构造就被破坏了,你也必须在子类中的构造方法做出相应的改变。
ps1:super只能在当前构造方法中使用

ps2:super在构造方法中必须处于第一行的位置,也侧面说明了,子类构造时必须先帮助父类先构造(super先进去构造父类,构造完再出来构造子类)


super的三种用法:
1.super();调用父类的构造方法,只能在构造方法中使用
2.super.func();调用父类的普通方法
3.super.data;调用父类的成员属性

用法1必须在构造方法中使用,2和3出现在哪里都无所谓,这些出现位置的用法和this非常相似,我们回顾一下this的用法:

1.this.data表示调用当前对象的属性
2.this.func()表示调用当前对象的方法
3.this()调用当前对象的其他构造方法——this()只能存在于构造方法中

注意!super不能出现在静态方法中,super是代表父类对象的引用,它是依赖于对象的,static修饰的静态方法是不依赖对象的,所以是不能共存的。this也一样,this是当前对象的引用,它也是依赖对象的,所以也不能放在静态方法中

重名

class Animal{public String name="abc";public int age;public void eat(){System.out.println(name+"正在吃东西");}public Animal(String name,int age){this.name=name;this.age=age;}
}
class Dog extends Animal{public String name;public Dog(String name,int age){super(name,age);}
}
public class Demo1 {public static void main(String[] args) {Dog d=new Dog("xiaoHei",1);System.out.println(d.name);}}

我们现在new了一个Dog类的d对象,传参过去xiaoHei和1(name参数和age参数),然后进入Dog类里面的构造方法,构造方法里的super把xiaoHei和1再传给父类,将父类里的name和age初始化了,现在问题是,我子类里面也有一个name,那我程序也没有报错啊,最后运行结果的name是谁呢?

我们用子类创建一个对象后,它的在内存里是这样的:

那同样的,我们现在如果Dog这个子类里面也有name这个属性,堆里面应该是有两个name,如果是子类应该是null,如果是父类应该是xiaoHei。


我们的运行结果是null,这里也就是涉及一个知识点:如果子类和父类有同名的字段了,那么是优先子类的字段。如果子类中没有父类的字段,用父类的

说个大白话:你爸爸买了一块蛋糕,你也买了一块蛋糕,你肯定优先吃你自己的蛋糕;你爸爸买了一块蛋糕,你没买蛋糕,你可以吃你爸爸的蛋糕。

那这个时候肯定也有一些小伙伴要问,那我如果子类和父类里面有同名字段,但我就是想用父类里面的东西,怎么办?
也有办法!就是我们之前说的super.name,不然默认还是优先子类

2.3protected关键字

我们在Animal里面多加一个count属性,并用private修饰

class Animal{public String name;public int age;private int count;public void eat(){System.out.println(name+"正在吃东西");}public Animal(String name,int age){this.name=name;this.age=age;}
}
class Dog extends Animal{public String name;public Dog(String name,int age){super(name,age);//super:显示调用构造方法// 这里的super(name,age)就是调用父类的两个参数构造方法}
}
public class Demo1 {int val=1;//不加任何访问修饰符,默认是包访问权限public static void main(String[] args) {Dog d=new Dog("xiaoHei",1);System.out.println(d.name);System.out.println(d.count);//这里会报错}
}

会发现,我们在调用count这个对象的时候,会报错,是因为private只能在当前类使用,出了当前类就不能直接调用private修饰的东西,那我们把private改成protect试一试:

class Dog extends Animal{public String name;public Dog(String name,int age){super(name,age);//super:显示调用构造方法// 这里的super(name,age)就是调用父类的两个参数构造方法}
}
public class Demo1 {int val=1;//不加任何访问修饰符,默认是包访问权限public static void main(String[] args) {Dog d=new Dog("xiaoHei",1);System.out.println(d.name);System.out.println(d.count);//这里会报错}
}

你会发现系统不再报错,为什么?我们先来看一张图

(图片来自比特就业课)
public修饰的:什么地方都能用
private修饰的:只有当前类可以用
default修饰的:同一包中都可以用,出了这个包就不能用了
protected修饰的:同一包都可以用,或者出了包,但是是子类也可以用。

我们如果用public或者private修饰一个类,有时就过于激进了,protected好就好在,它对于子类继承有一个判断——继承了可以访问,不继承不能访问

2.4更复杂的继承关系

刚才我们的例子中,只涉及到了Animal、Dog、Bird三种类,那如果情况更复杂呢?我们Animal下面还有猫,猫下面还有更多品种的猫:

(图片来自比特就业课)
这时候使用继承方式来表示,会涉及到更复杂的体系。

2.5final关键字

如果一个类不想被继承,我们可以设置为final,很好理解嘛:你不想生孩子继承你的花呗,那你就是你家族最后的final的一个。(哈哈哈开个玩笑)
ps:final的一些其他用法
final int a=10;//常量不可被修改
final class A //代表整个类不可以被继承
final修饰方法,后面再讲解

三、组合

和继承类似,组合也是一种表达类之间关系的方式,也能够达到代码重用的效果。组合是a part of…的意思,比如一个学校由学生和老师组成,那么学生是学校的一部分,老师也是学校的一部分。

(图片来自比特就业课)
我们组合不需要像继承那样写extends这样的关键字,仅仅是将一个类的实例作为另一类的字段(如上图)。这是我们设计类的一种常用方法之一。

四、多态

4.1向上转型(子类对象给父类)

刚才的例子中,我们可能会写形如下面的代码:

Bird bird=new Bird("肥波");
//我们这里假设构造方法里只接受name这一个参数

这个代码也可以写成如下形式

Bird bird=new Bird("肥波");//bird是一个引用
Animal bird2=bird;//bird2也是一个引用,
//这段代码表示bird2指向bird所指的对象
//或者
Animal bird2=new Bird("肥波");
//父类引用 来引用 子类对象

此时bird2是一个父类(Animal)的引用,指向一个子类Bird的实例,这种写法称为向上转型

什么时候会发生向上转型呢?
1.直接赋值:

Animal bird2=new Bird("肥波");

2.方法传参

public static void func(Animal animal){}
public static void main(String[] args) {Dog dog=new Dog("xiaoHei",1);func(dog);}
//dog是子类创建的对象,然后你把它传过去给父类创建的引用指向

3.作为返回值

 public static Animal func2(){Dog dog=new Dog("xiaoH",1);return dog;}

你在func2这个方法里new了一个dog对象,然后返回的时候把这个对象交给Animal这个类的引用来指向(你返回的时候肯定有东西接收嘛,接收那头肯定是Animal 引用名=返回值,还是相当于父类引用 引用了 子类对象

这种向上转型我们可以这样理解:
你让你女朋友去喂肥波,你可以说:“媳妇你喂小鸟了吗”或者“媳妇你喂肥波了吗”

4.2动态绑定

我们先来看一段代码:

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}public Animal(String name,int age){this.name=name;this.age=age;}
}
class Dog extends Animal{public Dog(String name,int age){super(name,age);}
}
public class Demo1 {public static void main(String[] args) {Dog dog=new Dog("xiaoHei",1);dog.eat();}
}


这里我们new了一个狗子,然后传参过去Dog类,由super传到Animal类。然后我们由dog.eat()调用eat方法,打印了xiaoHei正在吃东西。

但有时候,我们的子类和父类会出现同名的方法,那我们再去调用会发生甚么事呢?我们在原先代码上稍作修改:我们给狗子这个类多写一个eat方法区别于其他动物的eat。

class Animal{public String name;public int age;public void eat(){System.out.println(name+"正在吃东西");}public Animal(String name,int age){this.name=name;this.age=age;}
}
class Dog extends Animal{public String name;public Dog(String name,int age){super(name,age);}public void eat(){System.out.println(name+"正在吃骨头");}
}
public class Demo1 {public static void main(String[] args) {Dog dog=new Dog("xiaoHei",1);dog.eat();}
}

我们new了一个dog,然后传参过去Dog的类,再由super传参给Animal,如果我们Dog类里面没有eat方法,我们肯定会打印,xiaoHei正在吃东西,但是当子类里有和父类同名的eat会怎样呢?运行结果如下:

在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.(只有父类的方法用父类的,子类和父类都有同名的用子类的)

动态绑定:
1.父类引用,引用 子类的对象
2.通过这个父类引用 调用父类 和子类同名的覆盖方法

ps:同名的覆盖也就是下面要讲的重写(方法返回值相同,方法名相同,参数列表相同,内容不一定相同)

4.3方法重写

针对刚才的eat()方法来说:子类实现父类的同名方法,并且参数的类型和个数完全相同,这种情况称为覆盖、重写、覆写
重写要满足4个条件:
1.方法名相同
2.参数列表相同
3.返回值相同
4.父子类的情况下
一般重写的方法前我们加一个@Override

class Dog extends Animal{public String name;public Dog(String name,int age){super(name,age);}@Overridepublic void eat(){System.out.println(name+"正在吃骨头");}
}

@Override就是告诉别人,这个方法是重写的
重写的一些注意事项:
1.静态方法不能重写
2.子类的访问修饰限定符范围应大于等于父类访问修饰限定符
(比如子类是public,父类是protected)
3.private方法不允许重写
4.final修饰的方法也不允许重写

ps:重写和重载的区别:

(图片来自比特就业课)
重载的这种方式有时也称静态绑定,系统会根据你方法的参数来确定你要走哪个方法(重载的方法参数不一定相同),区别重写的动态绑定(函数名、参数、返回值必须相同)。

4.4理解多态

知道向上转型、动态绑定、方法重写后,我们就可以使用多态的形式来设计程序了。比如:我们可以写一些只关注父类的代码,就可以同时兼容各种子类的情况。
代码示例如下:

class shape{public void draw(){System.out.println("正在画画");}
}
class Cycle extends shape{@Overridepublic void draw(){//重写快捷按键:drSystem.out.println("正在画圆");}
}
class Flower extends shape{@Overridepublic void draw(){System.out.println("正在画花");}
}public class TestDemo {public static void main(String[] args) {shape s=new shape();s.draw();s=new Flower();s.draw();s=new Cycle();s.draw();}
}

运行结果如下:
到这里,我们就更深层次的理解多态了——通过一个引用调用同一个方法,会有不同的表现形式(取决于它引用哪个对象)

ps:1.多态的大前提——一定是向上转型
使用多态的好处是什么?

  1. 类调用者对类的使用成本进一步降低.
    封装是让类的调用者不需要知道类的实现细节.
    多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
    因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.
  2. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else
    比如我现在要打印如下的形状
    ①不用多态,如果子类比较少还好,但如果子类比较多else if就会非常麻烦
public static void drawShapes() { Rect rect = new Rect(); Cycle cycle = new Cycle(); Flower flower = new Flower(); String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; for (String shape : shapes) { if (shape.equals("cycle")) { cycle.draw(); } else if (shape.equals("rect")) { rect.draw(); } else if (shape.equals("flower")) { flower.draw(); } }
}

②用多态,这样的代码不仅简单而且高大上,容易让你的同学老师直呼:“牛逼哇塞!”

public static void drawShapes() { // 我们创建了一个 Shape 对象的数组. Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; for (Shape shape : shapes) { //for each,每次循环定义一个shape来接收数组shapes里的内容shape.draw(); }
}

4.5向下转型(父类对象给子类)

public static void main(String[] args) {Animal animal=new Dog("xiaoHei",1);//new Dog()给animal 相当于狗是动物Dog dog=(Dog)animal; //将父类引用强转给子类引用//相当于动物是狗(大多数情况有问题)dog.eat();}

但我们一般不建议这么写,就拿这个代码来说,很简单的道理:不是所有animal都是dog,这样很容易出错的!所有我们向下转型有一个大前提:就是你父类引用的对象要和你父类向下转型的对象匹配上,还是拿这块代码说:你animal接收了一个dog,那你animal后面向下转型只能给dog,不能给bird等等其他

public static void main(String[] args) {Animal animal=new Dog("xiaoHei",1);Bird bird=(Bird)animal; bird.fly();}

比如上面这块代码,我们new了一个狗赋给动物(相当于狗是动物),然后你又把动物赋给鸟,前后联系起来就相当于狗是鸟,这明显逻辑不通。代码运行时也确实会报错:类型转换异常。

为了让向下转型更加安全,我们有了instanceof,instanceof可以判断一个引用是否是某个类的实例,如果是,则返回true,这时再进行向下转型操作就比较安全了。

Animal animal=new Cat("小猫");
if(animal instanceof Bird){Bird bird=(Bird)animal;
}

4.6super关键字

前面的代码中,由于使用了重写机制,调用到的是子类的方法,如果需要在子类内部调用父类方法怎么办?可以使用super关键字。
super表示获取到父类实例的引用,涉及两种常见用法:
1.使用super来调用父类的构造器(前面已经讲过)

public Bird(String name){super(name);
}

2.使用super来调用父类的普通方法

public class Bird extends Animal { public Bird(String name) { super(name); } @Override public void eat(String food) { // 修改代码, 让子调用父类的接口. super.eat(food); System.out.println("我是一只小鸟"); System.out.println(this.name + "正在吃" + food); }
}

在这个代码中, 如果在子类的 eat 方法中直接调用 eat (不加super), 那么此时就认为是调用子类自己的 eat (也就是递归了). 而加上 super 关键字, 才是调用父类的方法

super和this有一些相似,但其中也有区别:

(图片来自比特就业课)

4.7在构造方法中调用重写的方法

我们先来看一段坑人的代码:

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 { public static void main(String[] args) { D d = new D(); }
}

这里我们new了一个D类对象,然后进去D,因为D中默认会有一个super(),这里没有写,但是我们要知道super是存在的,然后super进去父类B构造方法,B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func,此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0。

五、抽象类

在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).

5.1语法规则

abstract class Shape { //包含抽象方法的类也必须用abstract修饰public abstract void draw(); //抽象类中**必须**有一个抽象方法!!!,抽象方法要用abstract修饰public int a;//抽象类中可以有成员变量public void func(){//可以有成员方法System.out.println("一个普通方法");}
}
class Rect extends shape{@Override//子类如果继承抽象类,必须重写抽象类所有的抽象方法public void draw() {System.out.println("正在画方块");System.out.println(a);//子类继承抽象类可以访问抽象类里面的成员super.func();//也可以通过super来调用抽象类里面的普通方法}
}
class Cycle extends shape{@Overridepublic void draw(){//重写快捷按键:因为我们这里是draw方法,你按一个dr就显示出来了,其他方法以此类推//比如func()方法,你重写的时候,按一下fu电脑也可快速进行重写System.out.println("正在画圆");}
}
class Flower extends shape{@Overridepublic void draw(){System.out.println("正在画花");}
}

注意事项

  1. 抽象类不能直接实例化
shape shape = new Shape();
// 编译出错
Error:(30, 23) java: Shape是抽象的; 无法实例化
  1. 抽象方法不能是 private 的
    这个很好理解,抽象类要被其他子类继承的啊,你老爹把东西都藏起来了,你继承个啥啊
abstract class Shape { abstract private void draw();
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private
  1. 抽象类中可以包含其他的非抽象方法, 也可以包含字段. 这个非抽象方法和普通方法的规则都是一样的, 可以被重写,也可以被子类直接调用
abstract class Shape { //包含抽象方法的类也必须用abstract修饰public abstract void draw(); //抽象类中**必须**有一个抽象方法!!!,抽象方法要用abstract修饰public int a;//抽象类中可以有成员变量public void func(){//可以有成员方法System.out.println("一个普通方法");}
}
class Rect extends shape{@Override//子类如果继承抽象类,必须重写抽象类所有的抽象方法public void draw() {System.out.println("正在画方块");System.out.println(a);//子类继承抽象类可以访问抽象类里面的成员super.func();//也可以通过super来调用抽象类里面的普通方法}
}

一些特殊的注意点

1.如果一个抽象类继承了另一个抽象类,那这个抽象类可以不重写父类的抽象方法

abstract class shape{public int a;public void func(){System.out.println("一个普通方法");}public abstract void draw();/*{//System.out.println("正在画画");}*/
}
abstract class A extends shape{public abstract void func2();
}

2.但如果继承抽象类的抽象类再次被继承(比如上面的类A被继承了),你就必须把之前欠的抽象方法全部写上

abstract class shape{public int a;public void func(){System.out.println("一个普通方法");}public abstract void draw();/*{//System.out.println("正在画画");}*/
}
abstract class A extends shape{public abstract void func2();
}
class B extends A{@Overridepublic void draw() {//"爷爷的抽象方法"}@Overridepublic void func2() {//"爸爸的抽象方法"}
}

大家可以按我这个理解方式来进行简单的记忆:抽象方法相当于你开的花呗,你爸爸不还,你来还,你仍然不还,你儿子来还。。。出来混迟早要还的哈哈哈

5.2抽象类的作用

抽象类存在的最大意义就是为了被继承.
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法
这个时候可能会有人说:普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?确实如此. 但是使用抽象类相当于多了一重编译器的校验

使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
小结:

六、接口

接口是抽象类的更进一步(你可以理解它是一个特殊的抽象类). 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.

6.1语法规则


代码如下:

interface IShape{//接口用interface修饰//接口的所有方法都用public//抽象方法默认public abstractpublic abstract void draw();//抽象方法//直接写void draw()也可以,因为默认是public abstractdefault public void func(){//接口中普通方法不能有具体实现,如果一定要实现,在方法前加default//default是默认的意思,也就是该方法是接口默认方法System.out.println("第一个默认方法");}default public void func2(){//接口可以有n多个默认方法System.out.println("第二个默认方法");}public static void staticFunc(){//接口可以有多个静态方法System.out.println("静态方法");}public static final int a=1;//接口中也可以包含字段,但必须是静态常量(final static),//这里必须定义a后必须初始化(你是最终值,那你得有个最终值啊)//这里你直接写 int a=1;也可,因为是默认public static final的
}class Triangle implements IShape{//类和接口之间的关系是通过implements实现的//一个类实现了一个接口必须重写抽象方法(接口具有抽象类的性质:必须重写抽象类的抽象方法)// 默认方法/静态方法你愿意重写就重写,不愿意也可以不写@Overridepublic void draw() {System.out.println("画一个三角");}@Override//默认方法可重写,可不重写public void func(){System.out.println("重写接口中默认方法");}
}public class TestDemo {public static void main(String[] args) {IShape iShape=new Triangle();iShape.draw();iShape.func();}
}

运行结果如下:

一些注意点:
1.使用interface定义一个接口

2.接口中的方法一定是抽象方法,因此可以省略abstract

3.接口中的方法一定是public,因此可以省略public

4.我们代码中的Triangle使用implements继承接口,此时表达的含义不再是“扩展”而是“实现”

5.在调用的时候同样可以创建一个接口的引用,对应到一个子类的实例(向上转型,比如上述代码中的 IShape iShape=new Triangle();)

6.接口不能单独被实例化(你不能new一个接口)

扩展(extends):当前已有一些功能,进一步扩充功能
实现(implements):当前什么也没有,需从头构造

6.2实现多个接口

如下面代码中的c,如果想同时实现Ia和Ib两个接口,可以通过implements来实现,多个接口由逗号隔开。比如class c implements Ia,Ib 这里需要注意的是,实现多个接口,那你也必须需要重写每一个接口的抽象方法

interface Ia{void funcA();//默认为public abstract
}
interface Ib{void funcB();
}
class c implements Ia,Ib{@Overridepublic void funcA() {}@Overridepublic void funcB() {}
}

这里需要注意的是,抽象方法的顺序必须对应多个接口的顺序,比如Ia的必须对应funcA,Ib的必须对应funcB。

举例说明
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.现在我们通过类来表示一组动物

class Animal { protected String name; public Animal(String name) { this.name = name; }
}

另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”.

//不是所有动物都会飞、游泳等,我们不能直接把这些特性写到Animal类中
//一个类不能同时继承多个类,但可以实现多个接口
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 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 class test {public static void running(IRunning iRunning) {iRunning.run();}public static void swimming(ISwimming iSwimming) {iSwimming.swim();}public static void flying(IFlying iFlying){iFlying.fly();}public static void main(String[] args) {running(new Duck("鸭子"));//如果能成功传参一个说明这个对象具有接口里描述的能力//(对象的类已经实现了接口)flying(new Duck("鸭子"));swimming(new Fish("鱼"));}
}

运行结果如下:

这样设计有什么好处呢? 时刻牢记多态的好处, 让程序猿忘记类型. 有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力.

6.3接口的继承

接口和接口之间,可以使用extends来操作它们的关系(意为拓展),一个接口IB通过extends来拓展另一个接口IA的功能。当一个类Ic通过implements来实现接口IB时,重写的方法不仅仅是IB的抽象方法,还有IB从C接口拓展来的抽象方法funcA
代码示例如下:

interface IA{void funcA();
}
interface IB extends IA{//接口IB通过继承,也拥有了接口IA的功能void funcB();
}
class Ic implements IB{@Overridepublic void funcB() {}//只重写一个funcB会报错,因为你IB也继承了IA@Overridepublic void funcA() {}
}

6.4Clonable接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一

Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.

class Animal implements Cloneable {private String name;@Overridepublic Animal clone() {Animal o = null;try {o = (Animal)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return o;}}public class Test {public static void main(String[] args) {Animal animal = new Animal();Animal animal2 = animal.clone();System.out.println(animal == animal2);}}
// 输出结果
// false

浅拷贝 VS 深拷贝
Cloneable 拷贝出的对象是一份 “浅拷贝”
观察以下代码:

public class Test {static class A implements Cloneable {public int num = 0;@Overridepublic A clone() throws CloneNotSupportedException {return (A)super.clone();}}static class B implements Cloneable {public A a = new A();@Overridepublic B clone() throws CloneNotSupportedException {return (B)super.clone();}}public static void main(String[] args) throws CloneNotSupportedException {B b = new B();B b2 = b.clone();b.a.num = 10;System.out.println(b2.a.num);}}
// 执行结果
//10

通过 clone 拷贝出的 b 对象只是拷贝了 b 自身, 而没有拷贝内部包含的 a 对象. 此时 b 和 b2 中包含的 a 引用仍然是指向同一个对象. 此时修改一边, 另一边也会发生改变.
未来学到序列化的时候, 会告诉大家如何进行深拷贝.

6.6小结

抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(非常重要!! ).
核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
如之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此,此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口

class Animal { protected String name; public Animal(String name) { this.name = name; }
}

java面向对象编程精讲相关推荐

  1. Java面向对象编程——抽象类和接口

    Java面向对象编程--抽象类和接口 定义类的过程就是抽象和封装的过程,而抽象类与接口则是对实体类进行更高层次的抽象,进定义公共行为和特征. 抽象类: 如果一个类没有足够的信息去描述一个具体的对象,那 ...

  2. 八、Java面向对象编程(类、对象、方法、重载、可变参数、作用域、构造器、this本质)

    文章目录 Java面向对象编程(类.对象.方法.重载.可变参数.作用域.构造器.this本质) 一.类与对象 1. 类与对象的引出 2. 使用现有技术解决 3. 现有技术解决的缺点分析 4. 类与对象 ...

  3. 孙卫琴:我为什么要写《Java面向对象编程》

    孙卫琴:我为什么要写<Java面向对象编程> 特约作者:孙卫琴 策划 & 设计 & 制作:李大微 当<精通Struts>和<精通Hibernate> ...

  4. java面向对象编程基础

    java面向对象编程基础 前言:什么是java 是咖啡飘香的清晨 - 是斯坦福校园意浓情深 - 是James的思想睿智 是剁手党双十一挥舞的利刃 是大数据云计算驰骋的平台 - 是ATM上吐出的钞票 - ...

  5. Java面向对象编程(第2版)_学习记录

    <Java面向对象编程(第2版)> 孙卫琴 编著 文章目录 一.介绍 (一)平台与开发环境 (二)一些知识 (三)数组 二.类的生命周期 (一)类的加载 1. 加载 2. 连接 3. 初始 ...

  6. java面向对象编程知识点总结

    一:今天完成 上午详细了解了java面向对象编程的一些细节,记录如下. 1)类 是一种引用类型,包含一个签名和一个主体,主体是放在花括号里面的成员,成员包括字段和方法,还有构造方法.初始化程序和嵌套类 ...

  7. Java面向对象编程篇6——注解与反射

    Java面向对象编程篇6--注解与反射 1.注解概述 Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制 Java 语言中的类.方法.变量.参数和包等都可 ...

  8. Java面向对象编程篇5——枚举

    Java面向对象编程篇5--枚举 1.枚举的概念 在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事 物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型 2.枚举的定义 使用 ...

  9. Java面向对象编程篇4——内部类

    Java面向对象编程篇4--内部类 1.内部类的概念 当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类 (Inner),而这个内部类所在的类叫做外部类(Outer). 类中的内容:成员变 ...

最新文章

  1. MySQL关系数据库
  2. Myeclipse快捷键总结大全
  3. 初学Python——字符串相关操作
  4. python grpc unary call错误_python的黑魔法-装饰器
  5. python保留sqrt_python:quot;因式分解quot;引出的知识盲点
  6. Oracle 执行长SQL
  7. sharepoint 2013 配件控制FileUpload如何检查是否图像的方法
  8. 音阶频率对照表_八度音阶和频率的关
  9. js函数劫持与反劫持
  10. 过零检测法MATLAB仿真,过零检测 - MATLAB Simulink - MathWorks 中国
  11. 【云原生】SPL 提速天体聚类任务 2000 倍【文末送书】
  12. work english words
  13. SAP ECC 6.0 下载以及安装
  14. FPGA 任意分频器设计
  15. SwfObjects在页面中嵌入flash(SWF)
  16. 【工程/物理光学(五)——激光技术】
  17. 一个工程师对潘多拉开发板的使用体验
  18. 判断边界由1组成的最大正方形面积
  19. 使用日历热图进行时序数据可视化
  20. 大疆无人机参数解释YAW/Roll/Pitch

热门文章

  1. 设计原则-迪米特原则与合成复用原则
  2. 九宫格穷举所有解——九宫Killer
  3. python进程实现多任务
  4. 利用Python爬虫下载王者荣耀教学视频
  5. 【2019.5.31】学习·分享会·总结???
  6. PowerBuilder 颜色列表
  7. 某程序员毕业进UC,被阿里收购!跳去优酷土豆,又被阿里收购!再跳去饿了么,还被阿里收购!难道阿里想收购的是他?
  8. OSChina 周三乱弹 —— 就算登顶程序员的键盘王座
  9. 儿童思维导图绘制技巧
  10. Comparator接口的使用