继承
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。在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);
}
}