面向对象(下)

继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。在Java中用extends关键字表示继承。
继承:就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
继承的好处:
①继承的出现提高了代码的复用性。
②继承的出现让类与类之间产生了关系,提供了多态的前提。

class 父类 {
    ... 
}
class 子类 extends 父类 {
    ... 
}

注意:Java中类只能够单继承,不能多继承
在继承中子父类的成员关系(就近原则)
在子类方法中使用一个变量时:
首先,在方法的局部变量中找这个变量,有则使用;否则,在本类中找成员变量,有则使用;否则,在父类中找成员变量,有则使用;否则,报错。
用子类对象使用一个方法时:
首先,在子类中找这个方法,有则使用;否则,在父类中找这个方法,有则使用;否则,报错。
super关键字和this关键字的含义
super:代表父类的存储空间标识(可以理解为父类的引用)。
this:代表当前对象的引用(谁调用就代表谁)。
①访问成员

this.成员变量 //本类的
super.成员变量 //父类的
this.成员方法名() //本类的
super.成员方法名() //父类的

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super()和this()都必须是在构造方法的第一行,所以不能同时出现。
重载和重写
重载:在同一类中,同名不同参,返回值无关

class Demo {
    public int method(int i) {
        return i;
    }
    public int method(int i,int j) { //重载
        return i+j;
    }
}

重写:在子父类中,同名同参

class Fu {
    public String method(String name) {
        return "Fu: "+name;
    }
}
class Zi {
    public String method(String name) { //重写
        return "Zi: "+name;
    }
}

重写需要注意:
①子类方法的访问权限要大于等于父类方法的访问权限。
②静态只能重写静态。

Object类

它是所有对象的直接后者间接父类,该类中定义的肯定是都具备的功能,在java中每个类都继承了Object类。
Object类中的三个常用方法:

String toString():将一个对象返回成一个字符串的形式
boolean equals(Object obj):用于比较两个对象的引用是否相等
int hashCode():返回对象的哈希值

代码演示

public class ObjectDemo {
    public static void main(String[] args) {
        Student s1 = new Student ("zhangsan");
        Student s2 = new Student("lisi");
        System.out.println(s1.toString());
        System.out.println(s2.toString());
        //Student类重写了equals方法,比较两个对象的name值是否相同。
        System.out.println(s1.equals(s2));
    }
}
class Student {
    private String name;
    private int age;
    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public boolean equals(Object obj){ //重写equals()方法
        if(!(obj instanceof Demo))
            return false;
        Student d = (Student)obj;
        return (this.name == d.name);
    }
    public String toString() { //重写toString()方法
        return name+"..."+age;
    }
}

==和equals的区别:
==可以用于比较基本数据类型,比较的就是基本数据类型的值是否相等,它也可以用于比较引用数据类型,比较的是对象的地址值是否相等。
equals只能用于比较引用数据类型的。Object提供的equals方法默认比较的是对象地址值是否相同;自定义类中,如果重写了equals方法,那么就是按照你自己的需求来比较的。
怎么理解一个类既继承了Object类又继承了其他类呢?
首先,所有类的祖宗都是Object类,所有类只能有一个父亲。Java的单继承指的是一个类不能有多个父亲。
举个例子:
如果A没有继承任何类,那他的类层次关系默认是A–Object
如果A继承了类B,那他的类层次关系变为A–B--Object,Object是他爷爷,B是他唯一的父亲,B直接继承Objcet,A间接继承Object

抽象类

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类。
抽象方法:没有方法体的方法
抽象类:包含抽象方法的类
抽象方法
使用abstract关键字修饰方法,该方法就成了抽象方法,抽象方法没有方法体。

修饰符 abstract 返回值类型 方法名(参数列表);

抽象类
如果一个类包含抽象方法,那么该类必须是抽象类。

public abstract class 类名 { 
    抽象方法
}

抽象的使用
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。此时的方法重写,是子类对父类抽象方法的实现,我们将这种方法重写的操作,也叫做实现方法。

接口

接口是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,接口的内部主要就是封装了方法,接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类来完成。这样将功能的定义与实现分离,优化了程序设计。接口的定义,它与定义类方式相似,但是使用interface关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
引用数据类型有数组类型、类类型、接口类型等等。
接口的定义
它不能创建对象,但是可以被实现。一个实现接口的类,需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象类。

public interface 接口名称 { 
    //抽象方法 
}

接口的实现
类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用implements关键字。
子类实现接口
①必须重写接口中所有抽象方法。
②继承了接口的默认方法,即可以直接调用,也可以重写。

class 类名 implements 接口名 { 
    //重写接口中抽象方法
}

在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
注意:接口中定义功能,当需要具有该功能时,可以让类实现该接口,接口中只是声明了应该具备该方法,是功能的声明。在具体实现类中重写方法,实现功能,是方法的具体实现。
接口的多实现
了解了接口的特点后,那么想想为什么要定义接口,使用抽象类描述也没有问题,接口到底有啥用呢?
接口最重要的体现:解决多继承的弊端。将多继承这种机制在Java中通过多实现完成了。

interface InterA { //接口A
    public static final int NUM = 3;
    public abstract void show(); //抽象方法
}
interface InterB { //接口B
    	public abstract void function(); //抽象方法
}
class A implements InterA, InterB { //多实现
    	public void show() {}
	public void function() {}
}

类继承类同时实现接口
接口和类之间可以通过实现产生关系,同时也学习了类与类之间可以通过继承产生关系。当一个类已经继承了一个父类,它又需要扩展额外的功能,这时接口就派上用场了。
子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能。如果子类想要继续扩展其他类中的功能呢?这时通过实现接口来完成。

class Fu {
    	public void show() {}
}
interface Inter {
    	pulbic abstract void print();
}
class Zi extends Fu implements Inter {
    public void show() {
        System.out.println("基础功能.....");
   }
   public void print(){
       System.out.println("扩展功能.....");
   }
}

接口的出现避免了单继承的局限性。父类中定义的事物的基本功能,接口中定义的事物的扩展功能。
总结:接口在开发中的好处
①接口的出现扩展了功能。
②接口其实就是暴漏出来的规则。
③接口的出现降低了耦合性,即设备与设备之间实现了解耦。
接口和抽象类的区别
相同点:
①都位于继承的顶端,用于被其他类实现或继承
②都不能直接实例化对象
③都包含抽象方法,其子类都必须重写这些抽象方法
不同点:
①抽象类为部分方法提供实现,避免子类重复实现这些方法,提高代码重用性,而接口只能包含抽象方法
②一个类只能继承一个直接父类(可能是抽象类),却可以实现多个接口(接口弥补了Java的单继承)
③抽象类是这个事物中应该具备的内容,接口是这个事物中的额外内容
二者的选用:
优先选用接口,尽量少用抽象类。

多态

多态是继封装、继承之后,面向对象的第三大特性。 生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。
多态:是指同一行为,具有多个不同表现形式。
多态的体现:父类或接口的引用指向了自己的子类对象。

父类类型 变量名 = new 子类类型();
//父类类型:指子类对象继承的父类类型,或者实现的父接口类型
Fu f = new Zi();
Person p = new Student();

引用类型转换
多态的转型分为向上转型与向下转型两种:
向上转型:多态本身就是是子类类型向父类类型向上转换的过程,这个过程是默认的。 当父类引用指向一个子类对象时,便是向上转型。

父类类型 变量名 = new 子类类型(); 
如:Animal a = new Cat();

为什么要转型?
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥有而父类没有的方法。编译都错误,更别说运行了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做向下转型。
总结:向上转型(多态)不能调用子类拥有而父类没有的方法。
转型的异常
转型的过程中,一不小心就会遇到这样的问题,请看如下代码:

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。
所以,转换前,我们最好先做一个判断,代码如下:

public static void main(String[] args) { 
	//向上转型 
	Animal a = new Cat(); 
	a.eat(); //调用的是Cat的eat 
	//向下转型
	if(a instanceof Cat) { 
		Cat c = (Cat) a; 
		c.eat(); //调用的是Cat的eat()方法
	} else if (a instanceof Dog) { 
	Dog d = (Dog) a; 
       d.watchHouse(); //调用的是Dog的watchHouse()方法
	}  
}

多态练习:电脑的运行基于主板

public class Test {
   public static void main(String[] args) {
       MainBoard mb = new MainBoard();
       mb.run();
       //mb.usewangka(new WangKa()); //此时只能使用网卡
       mb.usePCI(null);
       mb.usePCI(new WangKa()); 
       //使用多态后,不仅可以使用网卡还能使用其他的,更为扩展
   }
}
interface PCI { //插槽
    public void open();
    public void close();
}
class WangKa implements PCI { //网卡
    public void open() {
        System.out.println("Wangka open");
    }
    public void close() {
        System.out.println("Wangka close");
    }
}
class MainBoard { //主板
    public void run() {
        System.out.println("mainboard run");
    }
    public void usewangka(WangKa w){
        w.open();
        w.close();
    }
    //PCI p = new WangKa() 接口型引用指向自己的子类对象。
    public void usePCI(PCI p) { //此时的好处是:只要是PCI的子类都能作为参数传递给该方法的参数,更为扩展
        if(p != null) {
            p.open();
            p.close();
        }
    }
}

内部类

将类写在其他类的内部,可以写在其他类的成员位置和局部位置,这时写在其他类内部的类就称为内部类,其他类也称为外部类。
什么时候使用内部类呢?
在描述事物时,若一个事物内部还包含其他可能包含的事物,比如在描述汽车时,汽车中还包含这发动机,这时发动机就可以使用内部类来描述。

class 汽车 { //外部类
    class 发动机 { //内部类
        //其他代码
    }
}

内部类分为成员内部类与局部内部类。
我们定义内部类时,就是一个正常定义类的过程,同样包含各种修饰符、继承与实现关系等。
在内部类中可以直接访问外部类的所有成员,因为内部类持有外部类的引用。
成员内部类
定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问
语法格式:

class 外部类 { 
    修饰符 class 内部类 {
        //其他代码
    }
}

代码演示

class Body { //外部类,身体
    private boolean life= true; //生命状态
    public class Heart { //内部类,心脏
   	    public void jump() {
           System.out.println("心跳")
           System.out.println("生命状态"+life); //访问外部类成员变量
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        //创建内部类对象
        //外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
    	Body.Heart bh = new Body().new Heart();
    	//调用内部类中的方法
    	bh.jump();
    }
}

内部类可以直接访问外部类中的成员,因为内部类持有外部类的引用;如果外部类需要访问内部类中的成员,那么必须创建内部类对象进行访问。
局部内部类
定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问
语法格式:

class 外部类 { 
    修饰符 返回值类型 方法名(参数) {
        class 内部类 {
            //其他代码
        }
    }
}

访问方式:在外部类方法中,创建内部类对象,进行访问
代码演示

class Party { //外部类,聚会
   	public void puffBall(){ // 吹气球方法
       class Ball { // 内部类,气球
             public void puff(){
                System.out.println("气球膨胀了");
             }
        }
        //在外部类中,创建内部类对象,调用内部类的puff方法
        new Ball().puff();
    }
}

匿名内部类
内部类是为了应对更为复杂的类间关系。查看源代码中会涉及到,而在日常业务中很难遇到。最常用到的内部类就是匿名内部类,它是局部内部类的一种。
作用:匿名内部类是创建某个类型子类对象的快捷方式。
语法格式:

new 父类名或者接口名() { 
    //重写父类方法或者实现接口中的方法,也可以自定义其他方法。 
}; 

代码演示

class Futher {
    private String name;
    public void show() {};
}
public class Demo {
    public static void main(String[],args) {
       new Futher() {
          String name;
          public void show(){ //重写父类中的方法
             System.out.print(this.name);
         }
         public void method(){} //自己的特有方法
     };
  } 
}

什么时候定义匿名内部类?
匿名内部类只是为了简化书写,匿名内部类有局限,通常定义匿名内部类时,该类方法不超过3个 。
如果该类里面方法较多,不推荐使用匿名内部类。

包的声明与访问

包的概念
在Java中,包其实就是我们电脑系统中的文件夹,只不过包里存放的是类文件。当类文件很多的时候,通常我们会采用多个包进行存放管理他们,这种方式称为分包管理。在项目中,我们将相同功能的类放到一个包中,方便管理。并且日常项目的分工也是以包作为边界。
包的声明格式
通常使用公司网址反写,可以有多层包,包名采用全部小写字母,多层包之间用"."连接

package 包名.包名.包名...;

例如:

package cn.itcast.test;
package com.itheima.util;

注意:声明包的语句,必须写在程序有效代码的第一行(注释不算)

package cn.itcast; //包的声明,必须在有效代码的第一行
import java.util.Scanner;
import java.util.Random;
public class Demo {
    public static void main(String[] args) {
        ....
    }
}

包的访问
在访问类时,为了能够找到该类,必须使用含有包名的类全名(包名.类名)

import java.util.Scanner;
import java.util.Random;
import cn.itcast.domian.Student;
public class Demo {
    public static void main(String[] args) {
        ....
    }
}

带有包的类,创建对象格式:包名.类名 变量名 = new包名.类名();
cn.itcast.domian.Student stu = new cn.itcast.domian.Student();
//前提:包的访问与访问权限密切相关,这里以一般情况来说,即类用public修饰的情况。

import导包
导包的格式:

import 包名.类名;

代码演示

import java.util.Random;
import java.util.Scanner;
public class Demo {
    public static void main(String[] args) {
        //创建对象
        Random r = new Random();
        Scanner sc = new Scanner(System.in);
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值