目录
一、面向对象
1.面向对象概述
(1)概述
Java语言是一种面向对象的程序设计语言,而面向对象思想是一种程序设计思想,我们在面向对象思想的指引下, 使用Java语言去设计、开发计算机程序。 这里的对象泛指现实中一切事物,每种事物都具备自己的属性和行为。面向对象思想就是在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算 机事件的设计思想。它区别于面向过程思想,强调的是通过调用对象的行为来实现功能,而不是自己一步一步的去 操作实现。
(2)举例
洗衣服:
面向过程:把衣服脱下来-->找一个盆-->放点洗衣粉-->加点水-->浸泡10分钟-->揉一揉-->清洗衣服-->拧干-->晾起来
面向对象:把衣服脱下来-->打开全自动洗衣机-->扔衣服-->按钮-->晾起来
(3)区别
面向过程:强调步骤。
面向对象:强调对象,这里的对象就是洗衣机。
(4)特点
面向对象思想是一种更符合我们思考习惯的思想,它可以将复杂的事情简单化,并将我们从执行者变成了指挥者。面向对象的语言中,包含了三大基本特征,即封装、继承和多态。
2.类和对象
(1)类
a.类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物。现实中,描述一类事物。
b.属性:就是该事物的状态信息。
c.行为:就是该事物能够做什么。
d.举例:小猫。
属性:名字、体重、年龄、颜色。 行为:走、跑、叫。
(2)对象
a.对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为。现实中,一类事物的一个实例:一只小猫。
b.举例:一只小猫。
属性:tom、5kg、2 years、yellow。 行为:溜墙根走、蹦跶的跑、喵喵叫。
(3)类与对象的关系
a.类是对一类事物的描述,是抽象的。
b.对象是一类事物的实例,是具体的。
c.类是对象的模板,对象是类的实体。
(4)面向对象和面向过程区别
a.面向过程:当你要去实现一个功能的时候,每一个具体的步骤,你都得亲力亲为,详细的处理每一个细节。
b.面向对象:当你需要实现一个功能的时候,不用关心具体的步骤,而是找一个已经具备该功能的人,来帮我做事。
public class Test {
public static void main(String[] args) {
int[] array = {10,20,30,90,100,10000};
//要打印数组中的元素 [10,20,30,90,100];
//面试过程思维
System.out.print("["); //]
for (int i = 0; i < array.length; i++) {
if ( i == array.length -1){ //如果是最后一个元素 加 ]
System.out.println(array[i] + "]");
}else { //否则 加逗号
System.out.print(array[i]+",");
}
}
System.out.println("============");
//面向对象方式编程
//找了一个JDK给我们提供好的Arrays工具类
//其中有有个toString方法,直接把数组变成你想要的格式.
System.out.println(Arrays.toString(array));
}
}
3.类的定义
(1)事物与类的对比
现实世界的一类事物:
a.属性:事物的状态信息。
b.行为:事物能够做什么。
Java中用class描述事物也是如此:
a.成员变量:对应事物的属性。
b.成员方法:对应事物的行为。
(2)类的定义格式
public class ClassName {
//成员变量
//成员方法
}
a.定义类:就是定义类的成员,包括成员变量和成员方法。
b.成员变量:和定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。
c.成员方法:和定义方法几乎是一样的。只不过把static去掉。
d.类的定义格式举例:
public class Student {
//成员变量
String name; //姓名
int age;//年龄
//成员方法
//学习的方法
public void study() {
System.out.println("好好学习,天天向上");
}
//吃饭的方法
public void eat() {
System.out.println("学习饿了要吃饭");
}
}
4.对象的使用
(1)对象的使用格式
a.创建对象:
类名 对象名 = new 类名();
b.使用对象访问类中的成员:
对象名.成员变量;
对象名.成员方法();
c.对象的使用格式举例:
public class Test {
public static void main(String[] args) {
//创建对象格式:类名 对象名 = new 类名();
Student s = new Student();
System.out.println("s:"+s);
//直接输出成员变量值
System.out.println("姓名:"+s.name); //null
System.out.println("年龄:"+s.age); //0
System.out.println("‐‐‐‐‐‐‐‐‐‐");
//给成员变量赋值
s.name = "赵六";
s.age = 18;
//再次输出成员变量的值
System.out.println("姓名:"+s.name); //赵六
System.out.println("年龄:"+s.age); //18
System.out.println("‐‐‐‐‐‐‐‐‐‐");
//调用成员方法
s.study(); // "好好学习,天天向上"
s.eat(); // "学习饿了要吃饭"
}
}
(2)成员变量的默认值
数据类型 | 默认值 | |
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000' | |
布尔(boolean) | false | |
引用类型 | 数组,类,接口 | null |
5.类与对象的练习
a.定义手机类:
public class Phone {
// 成员变量
String brand; //品牌
int price; //价格
String color; //颜色
// 成员方法
//打电话
public void call(String name) {
System.out.println("给"+name+"打电话");
}
//发短信
public void sendMessage() {
System.out.println("群发短信");
}
}
b.定义测试类:
public class Test {
public static void main(String[] args) {
//创建对象
Phone p = new Phone();
//输出成员变量值
System.out.println("品牌:"+p.brand);//null
System.out.println("价格:"+p.price);//0
System.out.println("颜色:"+p.color);//null
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐");
//给成员变量赋值
p.brand = "锤子";
p.price = 2999;
p.color = "棕色";
//再次输出成员变量值
System.out.println("品牌:"+p.brand);//锤子
System.out.println("价格:"+p.price);//2999
System.out.println("颜色:"+p.color);//棕色
System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐");
//调用成员方法
p.call("孙悟空");
p.sendMessage();
}
}
6.对象内存图
(1)一个对象,调用一个方法内存图
(2)两个对象,调用同一方法内存图
(3)一个引用,作为参数传递到方法中内存图
public class Test {
public static void main(String[] args) {
Phone one = new Phone();
one.brand = "华为";
one.color = "8号色";
one.price = 3999.0;
method(one);
}
//方法,参数是对象类型的参数
public static void method(Phone param){
System.out.println(param.brand);
System.out.println(param.color);
System.out.println(param.price);
}
}
(4)一个引用作为返回值
public class Test {
public static void main(String[] args) {
Phone phone = getPhone();
System.out.println(phone.brand);
System.out.println(phone.color);
System.out.println(phone.price);
}
public static Phone getPhone(){
Phone one = new Phone();
one.brand = "华为";
one.color = "黑色" ;
one.price = 4999.0;
return one;
}
}
7.成员变量和局部变量区别
变量根据定义位置的不同,我们给变量起了不同的名字。如下图所示:
区别:
1.定义的位置不一样。
a.局部变量: 在方法的内部。
b.成员变量: 在方法外部,类的内部。
2.作用范围不一样。
a.局部变量: 只有在方法当中才可以使用,出了方法就不能再使用。
b.成员变量: 整个类中都可以使用。
3.默认值不一样。
a.局部变量: 没有默认值,如果想使用,必须手动进行赋值。
b.成员变量: 如果没有赋值,会有默认值,规则和数组一样。
4. 内存的位置不一样。
a.局部变量: 位于栈内存。
b.成员变量: 位于堆内存。
5.生命周期不一样。
a.局部变量: 随着方法进栈诞生的,随着方法出栈而消失。
b.成员变量: 随着对象的创建而诞生,随着对象被垃圾回收而消失。
public class Test {
//在方法的外面,类的里面定义的变量就交成员变量
String name; //成员变量
public void method(){
int num; // 局部变量
System.out.println(name);
}
public void methodB(int param){ //局部变量
// System.out.println(num);// 错误写法
int age;
// System.out.println(age);//没有赋值不能使用
System.out.println(param); //因为调用方法的时候肯定得给赋值
}
}
二、封装
1.封装概述
(1)概述
面向对象编程语言是对客观世界的模拟,客观世界里成员变量都是隐藏在对象内部的,外界无法直接操作和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。要访问该类的数据,必须通过指定的方式。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
(2)原则
将属性隐藏起来,若需要访问某个属性,提供公共方法对其访问。
2.封装的步骤
a.使用 private 关键字来修饰成员变量。
b.对需要访问的成员变量,提供对应的一对 getXxx 方法 、 setXxx 方法。
public class Test {
public static void main(String[] args) {
int[] array = {10,30,20,15,50};
int max = getMax(array);
System.out.println(max);
}
public static int getMax(int[] array){
int max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max){
max = array[i];
}
}
return max;
}
}
3.封装的操作——private关键字
(1)private的含义
a.private是一个权限修饰符,代表最小权限。
b.可以修饰成员变量和成员方法。
c.被private修饰后的成员变量和成员方法,只在本类中才能访问。
(2)private的使用格式
private 数据类型 变量名 ;
a.使用 private 修饰成员变量,代码如下:
public class Student {
private String name;
private int age;
}
b.提供 getXxx 方法 / setXxx 方法,可以访问成员变量,代码如下:
public class Student {
private String name;
private int age;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAge(int a) {
age = a;
}
public int getAge() {
return age;
}
}
4.封装优化1——this关键字
(1)this的含义
this代表所在类的当前对象的引用(地址值),即对象自己的引用。
tips :方法被哪个对象调用,方法中的this就代表那个对象。即谁在调用,this就代表谁。
(2)this使用格式
this.成员变量名;
使用 this 修饰方法中的变量,解决成员变量被隐藏的问题,代码如下:
public class Student {
private String name;
private int age;
public void setName(String name) {
//name = name;
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
//age = age;
this.age = age;
}
public int getAge() {
return age;
}
}
tips:方法中只有一个变量名时,默认也是使用 this 修饰,可以省略不写。
5.封装优化2——构造方法
当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
tips:无论你与否自定义构造方法,所有的类都有构造方法,因为Java自动提供了一个无参数构造方法,一旦自己定义了构造方法,Java自动提供的默认无参数构造方法就会失效。
(1)构造方法的定义格式
修饰符 构造方法名(参数列表){
// 方法体
}
构造方法的写法上,方法名与它所在的类名相同。它没有返回值,所以不需要返回值类型,甚至不需要void。使用 构造方法后,代码如下:
public class Student {
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public Student(){
System.out.println("无参数的构造方法");
}
public Student(String name,int age){
System.out.println("执行了有参构造方法....");
this.name = name;
this.age = age;
}
}
(2)注意事项
a.如果你不提供构造方法,系统会给出无参数构造方法。
b.如果你提供了构造方法,系统将不再提供无参数构造方法。
c.构造方法是可以重载的,既可以定义参数,也可以不定义参数。
d.构造方法名必须和类名完全一致,大小写一样。
e.定义有参的构造方法的时候,最好也写一个无参的构造方法。
f.构造方法中不能有返回值类型,void也不可以。
6.标准代码——JavaBean
JavaBean 是 Java语言编写类的一种标准规范。符合 JavaBean 的类,要求类必须是具体的和公共的,并且具有无 参数的构造方法,提供用来操作成员变量的 set 和 get 方法。
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//成员方法
//getXxx()
//setXxx()
}
a.编写符合 JavaBean 规范的类,以学生类为例,标准代码如下:
public class Student {
//成员变量
private String name;
private int age;
//构造方法
public Student() {
}
public Student(String name,int age) {
this.name = name;
this.age = age;
}
//成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
b.测试类,代码如下:
public class Test {
public static void main(String[] args) {
//无参构造使用
Student s= new Student();
s.setName("张三");
s.setAge(18);
System.out.println(s.getName()+"‐‐‐"+s.getAge());
//带参构造使用
Student s2= new Student("李四",18);
System.out.println(s2.getName()+"‐‐‐"+s2.getAge());
}
}
三、 继承
1.概述
(1)由来
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。 继承描述的是事物之间的所属关系, 我们通过继承,可以使多种事物之间形成一种关系体系。
(2)定义
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
(3)好处
a.提高代码的复用性。
b.类与类之间产生了关系,是多态的前提。
2.继承的格式
通过extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}class 子类 extends 父类 {
...
}
继承演示,代码如下:
/**
定义员工类Employee,做为父类
*/
class Employee {
String name; // 定义name属性
// 定义员工的工作方法
public void work() {
System.out.println("尽心尽力地工作");
}
}
/**
定义讲师类Teacher 继承 员工类Employee
*/
class Teacher extends Employee {
// 定义一个打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}
/*
定义助教类 继承员工类Employee
*/
class Assistant extends Employee {
//定义打印名称
public void workEat(){
System.out.println("name" + name);
}
}
/** 定义测试类 */
public class Test {
public static void main(String[] args) {
// 创建一个讲师类对象
Teacher t = new Teacher();
// 为该员工类的name属性进行赋值
t.name = "小明";
// 调用该员工的printName()方法
t.printName();// name = 小明
// 调用Teacher类继承来的work()方法
t.work(); // 尽心尽力地工作
// 创建一个助教类对象
Assistant ass = new Assistant();
// 为该员工类的name属性进行赋值
ass.name = "小红";
// 调用该员工的printName()方法
ass.printName();// name = 小红
// 调用Assistant类继承来的work()方法
ass.work(); // 尽心尽力地工作
}
}
3.继承后的特点——成员变量
在父子类的继承关系中,如果成员变量重名,则创建子类对象,访问有两种方式:
a.直接通过子类对象访问成员变量:等号左边是谁,就优先使用谁,没有则向上找。
b.间接的通过成员方法去访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
(1)成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu(){
//使用的是本类中的num, 不会向子类找
System.out.println(num);
}
}
public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi(){
//因为本类中有num 所以用的是本类的num,如果没有会去父类找
System.out.println(num);
}
}
public class Test {
public static void main(String[] args) {
Fu fu = new Fu();
System.out.println(fu.numFu);
Zi zi = new Zi();
System.out.println(zi.numFu);//10
System.out.println(zi.numZi);//20
System.out.println("================");
System.out.println(zi.num);//200
// System.out.println(zi.abc); // 父类也没有就会报错.
System.out.println("======");
//这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi();
//这个方法是在父类中定义.
zi.methodFu();
}
}
(2) 成员变量重名
如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:
class Fu {
// Fu中的成员变量。
int num = 5;
}
public class Zi extends Fu{
int num = 20;
public void method(){
int num = 30;
System.out.println(num);// 30 局部变量.
System.out.println(super.num);//10 父类成员变量
System.out.println(this.num);// 20 本类成员变量
}
}
/*
局部变量: 直接写成员变量名 优先使用局部变量。
本类的成员变量: this.成员变量名。
父类的成员变量: super.成员变量名。
*/
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();// 30
}
}
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super
关键字,修饰父类成员变量 ,使用格式:
super.父类成员变量名
子类方法需要修改,代码如下:
class Zi extends Fu {
// Zi中的成员变量
int num = 6;
public void show() {
//访问父类中的num
System.out.println("Fu num=" + super.num);
//访问子类中的num
System.out.println("Zi num=" + this.num);
}
}
tips: Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,可以在父类中提供公共的getXxx方法和setXxx方法。
4.继承后的特点——成员方法
(1)成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的。对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。代码如下:
class Fu{
public void show(){
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu{
public void show2(){
System.out.println("Zi类中的show2方法执行");
}
}
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
(2)成员方法重名——重写(Override)
如果子类父类中出现重名的成员方法,该访问是一种特殊情况,叫做方法重写(Override)。方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。代码如下:
public class Fu {
public void methodFu(){
System.out.println("父类方法执行!!!");
}
public void method(){
System.out.println("父类重名方法执行...");
}
}
public class Zi extends Fu {
public void methodZi(){
System.out.println("子类方法执行!");
}
public void method(){
System.out.println("子类方法重名方法执行...");
}
}
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
zi.methodFu();
zi.methodZi();
//new 的是子类的对象,所以优先使用子类
zi.method();
}
}
(3)重写注意事项
a.必须保证父类和子类之间方法名称相同,参数列表相同,@Override:写在方法前面,用来检测是不是有效的重写.,不满足重写条件,则报错。
b.子类方法的返回值必须要小于等于父类的返回值范围。
c.子类方法的权限必须大于等于父类方法的权限修饰符(public > protected > (default一般都是省略不写) > private)。
(4)重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:
public class Phone {
public void call(){
System.out.println("打电话功能.....");
}
public void send(){
System.out.println("发短信...");
}
public void show(){
System.out.println("显示电话号码...");
}
}
//智能手机类
public class NewPhone extends Phone {
@Override
public void show(){
super.show(); //把父类的方法拿过来重复使用.
//自己子类后期添加的更多功能
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
phone.call();
phone.send();
phone.show();
System.out.println("====================");
NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.send();
newPhone.show();
}
}
tips:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
(5)注意事项
a.子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
b.子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
5.继承后的特点——构造方法
定义格式和作用:
a.构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
b.构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super()
,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:
public class Fu {
public Fu(){
System.out.println("父类无参构造方法!");
}
public Fu(int num){
System.out.println("父类有参的构造方法"+num);
}
}
public class Zi extends Fu {
public Zi(){
super(20); //子类调用父类重载的构造方法
System.out.println("子类的构造方法...");
}
public void method(){
///super(); 错误写法,只有在子类构造方法中才能调用父类的构造犯法
}
}
public class Test {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
继承关系中,父子类构造方法访问的特点:
a.子类的构造方法当中有一个默认隐含的 "super()" 调用,所以一定要先先调用父类的的构造方法,后执行的子类的构造方法。
b.可以通过super关键字在子类中调用父类的重载构造方法。
c.super的父类构造方法调用,必须写到子类的构造方法的第一行,一个子类的构造方法不能调用多次父类的构造方法,只有在子类构造方法中才能调用父类的构造方法 。
6.super和this
(1)父类空间优先于子类对象产生
在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空 间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构 造方法调用时,一定先调用父类的构造方法。
(2)super和this的含义
a.super :代表父类的存储空间标识(可以理解为父类的引用)。
b.this :代表当前对象的引用(谁调用就代表谁)。
(3)super和this的用法
a.访问成员
this.成员变量 ‐‐ 本类的
super.成员变量 ‐‐ 父类的
this.成员方法名() ‐‐ 本类的
super.成员方法名() ‐‐ 父类的
b.访问构造方法
this(...) ‐‐ 本类的构造方法
super(...) ‐‐ 父类的构造方法
tips:子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
(4)super关键字的三种用法
a.在子类成员方法中访问父类的成员变量。
b.在子类的成员方法中,访问父类的方法。
c.在子类的构造方法中,访问父类的构造方法。
public class Fu {
int num = 50;
public void method(){
System.out.println("父类方法");
}
}
public class Zi extends Fu {
int num = 20;
public Zi(){
super();//在子类的构造方法中,访问父类的构造方法.
}
public void methodZi(){
System.out.println(super.num);//父类的
}
//在子类的成员方法中,访问父类的方法
public void method(){
super.method();
System.out.println("子类方法");
}
}
public class Fu {
int num = 100;
}
public class Zi extends Fu {
public Zi(){
this(10);
}
public Zi(int n){
}
public Zi(int a, int b){
}
int num = 20;
public void showNum() {
int num = 10;
System.out.println(num);//局部变量 10
System.out.println(this.num);//20 本类中的成员变量
System.out.println(super.num); //父类中的成员变量
}
public void methodA() {
System.out.println("AAAAA");
}
public void methodB(){
this.methodA();
System.out.println("BBBBB");
}
}
(5)this关键字用法
a.在本类的成员方法中,访问本类的成员变量。
b.在本类的成员方法中,访问本类的另一个成员方法。
c.在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法中要注意:
a. this(....)调用必须是构造方法的第一个语句,唯一一个。
b. super和this两种构造调用,不能同时使用。
public class Fu {
int num = 30;
}
public class Zi extends Fu {
public Zi(){
this(20);//本类的无参构造,调用本类的有参构造.
//this(22,12);//错误写法
}
public Zi(int n){
this(20,12);
}
public Zi(int a,int b){
}
int num = 20;
public void showNum(){
int num = 10;
System.out.println(num); //局部变量
System.out.println(this.num);//本类中的成员变量
System.out.println(super.num);//父类中的成员变量
}
public void methodA(){
System.out.println("AAA");
}
public void methodB(){
this.methodA();// 起到强调作用
System.out.println("BBB");
}
}
7.继承的特点
a.Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //ok
class C extends A,B... //error
b.ava支持多层继承(继承体系)。
class A{}
class B extends A{}
class C extends B{}
tips:顶层父类是Object类。所有的类默认继承Object,作为父类。
c.子类和父类是一种相对的概念。
四、多态
1.概述
(1)引入
多态是继封装、继承之后,面向对象的第三大特性。生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也 是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
(2)定义
多态: 是指同一行为,具有多个不同表现形式。
(3)前提
a.继承或者实现(二选一 )。
b.方法的重写(意义体现:不重写,无意义 )。
c.父类引用指向子类对象(格式体现 )。
2.多态的体现
a.多态体现的格式:
父类类型 变量名 = new 子类对象;
变量名.方法名();
tips:父类类型:指子类对象继承的父类类型,或者实现的父接口类型。
代码如下:
Fu f = new Zi();
f.method();
b.当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写 后方法。代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 多态形式,创建对象
Animal a1 = new Cat();
// 调用的是 Cat 的 eat
a1.eat();
// 多态形式,创建对象
Animal a2 = new Dog();
// 调用的是 Dog 的 eat
a2.eat();
}
}
3.多态下的成员变量访问
访问成员变量的的两种方式:
a.直接通过对象名称访问成员变量,看等号左边是谁,优先用谁,没有则向上找。
b.间接通过成员方法访问:看该方法属于谁,优先用谁,没有则向上找。
(1)成员变量重名
父类引用指向子类对象的方式,访问成员变量,会访问父类里的成员变量。
//父类
public class Fu {
int num = 10;
}
//子类
public class Zi extends Fu {
int num =20;
}
public class Test{
public static void main(String[] args){
Fu fu = new Zi();
System.out.println(fu.num);
}
}
(2)间接通过成员方法访问成员变量
public class Fu {
int num = 10;
public void show(){
System.out.println(num);
}
}
public class Zi extends Fu {
int num =20;
int age = 30;
}
public class Test {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.num);
System.out.println("=============");
fu.show();//打印10
}
}
4.多态下的成员方法访问
在多态下成员方法的访问规则是: 看new的是谁,就优先使用谁,没有则向上找(编译看左边,运行看右边)。
//父类
public class Fu {
public void method(){
System.out.println("父类方法");
}
public void methodFu(){
System.out.println("父类特有方法!!");
}
}
//子类
public class Zi extends Fu {
@Override
public void method(){
System.out.println("子类方法");
}
public void methodZi(){
System.out.println("子类特有方法");
}
}
public class Demo02Mutil {
public static void main(String[] args) {
Fu fu = new Zi();// 多态
fu.method();//父子都有,优先用子
fu.methodFu();//子类没有,父类有,向上找到父类
//编译看左边,左边是Fu,父当中没有methodZi()方法,所以编译报错
//fu.methodzi();//错误写法
}
}
5.多态的好处
实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用,更能体现出多态的扩展 性与便利。代码如下:
定义父类:
public abstract class Animal {
public abstract void eat();
}
定义子类:
public class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
public class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
Cat c = new Cat();
Dog d = new Dog();
// 调用eat
c.eat();
// 调用 d
d.eat();
/*
以上两个方法,可以使用多态方式调用
而执行效果一致
*/
// 多态形式,创建对象
Animal a = new Cat();
Animal b = new Dog();
a.eat();
b.eat();
}
}
由于多态特性的支持,无论右边new的时候换成哪个子类对象,等号左边调用方法都不会发生变化。
6.引用类型转换
多态的转型分为向上转型与向下转型两种:
a.向上转型。
b.向下转型。
(1)向上转型
向上转型:多态本身是子类类型向父类类型向上转换的过程,这个过程是默认的。当父类引用指向一个子类对象时,便是向上转型。
使用格式:
父类类型 变量名 = new 子类类型();
如:Animal a = new Cat();
(2)向下转型
向下转型:父类类型向子类类型向下转换的过程,这个过程是强制的。一个已经向上转型的子类对象,将父类引用转为子类引用,可以使用强制类型转换的格式,便是向下转型。
使用格式:
子类类型 变量名 = (子类类型) 父类变量名;
如:Cat c =(Cat) a;
(3)转型原因
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有,而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子 类特有的方法,必须做向下转型。转型演示,代码如下:
定义类:
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void catchMouse() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void watchHouse() {
System.out.println("看家");
}
}
定义测试类:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
Cat c = (Cat)a;
c.catchMouse();// 调用的是 Cat 的 catchMouse
}
}
(4)转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat();
// 调用的是 Cat 的 eat
// 向下转型
Dog d = (Dog)a;
d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
}
}
这段代码可以通过编译,但是运行时,却报出了ClassCastException
,类型转换异常!这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。为了避免ClassCastException的发生,Java提供了instanceof
关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。
所以,转换前,我们最好先做一个判断(instanceof),代码如下:
public class Test {
public static void main(String[] args) {
// 向上转型
Animal a = new Cat();
a.eat(); // 调用的是 Cat 的 eat
// 向下转型
if (a instanceof Cat){
Cat c = (Cat)a;
c.catchMouse();// 调用的是 Cat 的 catchMouse
}
if (a instanceof Dog){
Dog d = (Dog)a; d.watchHouse(); // 调用的是 Dog 的 watchHouse
}
}
}