继承
继承的概念
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为
类的继承格式
在java中通过extends关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
class 父类{}class 子类 extends 父类{}
继承类型
继承的特性
- 子类拥有父类非private的属性、方法
- 子类可以拥有自己的属性和方法,即子类可以堆父类进行扩展
- 子类可以用自己的方式实现父类的方法
- java的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如B类继承A类,所以按照关系就是B类是C类的父类,A类是B类的父类,这是java继承区别与C++继承的一个特性
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)
继承关键字
继承可以使用extends和implements这两个关键字来实现继承,而且所有的类都是继承与java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在java.lang包中,所以不需要import)祖先类extends关键字
在java中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以extends只能继承一个类 ```java public class Animal { private String name;
private int id; public Animal(String myName, String myid) {
} public void eat() { //吃东西方法的具体实现 } public void sleep() { //睡觉方法的具体实现 } }//初始化属性值
public class Penguin extends Animal{ }
<a name="8J5gQ"></a>### implements关键字使用implements关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)```javapublic interface A {public void eat();public void sleep();}public interface B {public void show();}public class C implements A,B {}
super与this关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类
this关键字:指向自己的引用
class Animal {void eat() {System.out.println("animal : eat");}}class Dog extends Animal {void eat() {System.out.println("dog : eat");}void eatTest() {this.eat(); // this 调用自己的方法super.eat(); // super 调用父类方法}}public class Test {public static void main(String[] args) {Animal a = new Animal();a.eat();Dog d = new Dog();d.eatTest();}}//结果://animal : eat//dog : eat//animal : eat
final关键字
final关键字声明类可以把类定位为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:
声明类:
final class 类名 {//类体}
声明方法:
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}
实例方法也可以被定义为final,被定义为final的变量不能被修改;被声明为final类的方法自动地声明为final,但是实例变量并不是final
构造器
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式);如果父类的构造器有参数,则必须在子类的构造器中显式地通过super关键字调用父类的构造器并配以适当的参数列表
如果父类构造器没有参数,则在子类的构造器中不需要食用super关键字调用父类构造器,系统会自动调用父类的无参构造器class SuperClass {private int n;SuperClass(){System.out.println("SuperClass()");}SuperClass(int n) {System.out.println("SuperClass(int n)");this.n = n;}}// SubClass 类继承class SubClass extends SuperClass{private int n;SubClass(){ // 自动调用父类的无参数构造器System.out.println("SubClass");}public SubClass(int n){super(300); // 调用父类中带有参数的构造器System.out.println("SubClass(int n):"+n);this.n = n;}}// SubClass2 类继承class SubClass2 extends SuperClass{private int n;SubClass2(){super(300); // 调用父类中带有参数的构造器System.out.println("SubClass2");}public SubClass2(int n){ // 自动调用父类的无参数构造器System.out.println("SubClass2(int n):"+n);this.n = n;}}public class TestSuperSub{public static void main (String args[]){System.out.println("------SubClass 类继承------");SubClass sc1 = new SubClass();SubClass sc2 = new SubClass(100);System.out.println("------SubClass2 类继承------");SubClass2 sc3 = new SubClass2();SubClass2 sc4 = new SubClass2(200);}}
输出结果为:
------SubClass 类继承------SuperClass()SubClassSuperClass(int n)SubClass(int n):100------SubClass2 类继承------SuperClass(int n)SubClass2SuperClass()SubClass2(int n):200
重写(Override)与重载(Overload)
重写(Override)
重写是子类对父类的允许访问的方法的实习过程进行重新编写,返回值和形参都不能改变;即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为;也就是说子类能够根据需要是实现父类的方法
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常;异常:父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常
在面向对象原则里,重写意味着可以重写任何现有方法
方法的重写规则
- 参数列表与被重写方法的参数列表必须完全相同
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
- 访问权限不能比父类中被重写的方法的访问权限更低;例如:如果父类的一个方法被声明为public,那么在子类中重写该访问就不能声明为protected
- 父类的成员方法只能被它的子类重写
- 声明为final的方法不能被重写
- 声明为static的方法不能被重写,但是能够被再次声明
- 子类与父类在同一个包中,那么子类可以重写父类的所有方法,除了声明为private和final的方法
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常;但是。重写的方法不能抛出新的强制性异常,或者比被重写方法声明更广泛的强制性异常,反之则可以
- 构造方法不能被重写
- 如果不能继承一个类,则不能重写该类的方法
Super关键字的使用
当需要在子类中调用父类的被重写方法时,要使用super关键字 ```java class Animal{ public void move(){
} }System.out.println("动物可以移动");
class Dog extends Animal{ public void move(){ super.move(); // 应用super类的方法 System.out.println(“狗可以跑和走”); } }
public class TestDog{ public static void main(String args[]){
Animal b = new Dog(); // Dog 对象b.move(); //执行 Dog类的方法
} }
<a name="eLnfs"></a>## 重载(Overload)重载(overloading)是在一个类里面,方法名字相同,而参数不同;返回类型可以相同也可以不同<br />每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表<br />最常用的地方就是构造器的重载<a name="JHeGZ"></a>### 重载规则:- 被重载的方法必须改变参数列表(参数个数或类型不一样)- 被重载的方法可以改变返回类型- 被重载的方法可以改变访问修饰符- 被重载的方法可以声明新的或更广的检查异常- 方法能够在同一个类中或者在一个子类中被重载- 无法以返回值类型作为重载函数的区别标准<a name="2k7hN"></a>### 实例```javapublic class Overloading {public int test(){System.out.println("test1");return 1;}public void test(int a){System.out.println("test2");}//以下两个参数类型顺序不同public String test(int a,String s){System.out.println("test3");return "returntest3";}public String test(String s,int a){System.out.println("test4");return "returntest4";}public static void main(String[] args){Overloading o = new Overloading();System.out.println(o.test());o.test(1);System.out.println(o.test(1,"test3"));System.out.println(o.test("test4",1));}}
重写与重载之间的区别
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成堕胎的具体表现形式
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或者数量相同而类型和次序不同,则称为方法的重载(Overloading)
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一个多态性表现
多态
多态是同一个行为具有多个不同表现形式或形态的能力
多态就是同一个接口,使用不同的实例而执行不同操作
多态性是对象多种表现形式的体现
多态的优点

class Shape {void draw() {}}class Circle extends Shape {void draw() {System.out.println("Circle.draw()");}}class Square extends Shape {void draw() {System.out.println("Square.draw()");}}class Triangle extends Shape {void draw() {System.out.println("Triangle.draw()");}}
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法;
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理
以下时一个多态实例的演示:
public class Test {public static void main(String[] args) {show(new Cat()); // 以 Cat 对象调用 show 方法show(new Dog()); // 以 Dog 对象调用 show 方法Animal a = new Cat(); // 向上转型a.eat(); // 调用的是 Cat 的 eatCat c = (Cat)a; // 向下转型c.work(); // 调用的是 Cat 的 work}public static void show(Animal a) {a.eat();// 类型判断if (a instanceof Cat) { // 猫做的事情Cat c = (Cat)a;c.work();} else if (a instanceof Dog) { // 狗做的事情Dog c = (Dog)a;c.work();}}}abstract class Animal {abstract void eat();}class Cat extends Animal {public void eat() {System.out.println("吃鱼");}public void work() {System.out.println("抓老鼠");}}class Dog extends Animal {public void eat() {System.out.println("吃骨头");}public void work() {System.out.println("看家");}}
结果为:
吃鱼抓老鼠吃骨头看家吃鱼抓老鼠
虚函数
虚函数的存在是为了多态
java中其实没有虚函数的概念,它的普通函数就相当于C++的虚函数,动态绑定时Java的默认行为;如果Java中不希望某个函数具有虚函数的特性,可以加上final关键字变成非虚函数
重写
当子类对象调用重写的方法时,调用的时子类的方法,而不是父类中被重写的方法
要想调用父类中被重写的方法,则必须使用关键字 super
多态的实现方式
抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其他功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用;也是因为这个原因,通常在设计阶段决定要不要设计抽象类
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法
在Java中抽象类表示的是一个继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口
抽象类
在Java语言中使用abstract class来定义抽象类
继承抽象类
我们不能实例化一个抽象类的对象,但是可以通过继承的方式,去实例化抽象类的子类。且通过该方法可以设置或获取抽象类的成员变量
抽象方法
如果要设计一个类,该类包含了一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法
Abstract关键字可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号
public abstract class test{public abstract doubel testdoubel();}
声明抽象方法会造成以下两个结果:
- 如果一个类包含抽象方法,那么该类必须是抽象类
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类
继承抽象方法的子类必须重写该方法;否则,该子类也必须声明为抽象类;最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象
抽象类总结规定
- 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过;只有抽象的非抽象子类可以创建对象
- 抽象类中不一定包含抽象方法,但是又抽象方法的类必定是抽象类
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能
- 构造方法,类方法(用static修饰的方法)不能声明为抽象方法
抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类
封装
在面向对象程式设计方法中,封装是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问
要访问该类的代码和数据,必须通过严格的接口控制
封装最主要的功能在于我们能修改自己的实现代码,而不用修改拿下调用我们代码的程序片段
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性封装的优点
良好的封装能够减少耦合
- 类内部的结构可以自由修改
- 可以对成员变量进行更精确的控制
-
实现Java封装的步骤
修改属性的可见性来限制对属性的访问(一般限制为private)
public class Person{private String name;private int age;}
对每个值属性提供对外的公共方法访问,也就是创建一堆赋取值方法,用于对私有属性的访问,,例如: ```java public class Person{ private String name; private int age;
public int getAge(){ return age; }
public String getName(){ return name; }
public void setAge(int age){ this.age = age; }
public void setName(String name){ this.name = name; }
采用this关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突<a name="4tTIe"></a>## 实例```javapublic class EncapTest{private String name;private String idNum;private int age;public int getAge(){return age;}public String getName(){return name;}public String getIdNum(){return idNum;}public void setAge( int newAge){age = newAge;}public void setName(String newName){name = newName;}public void setIdNum( String newId){idNum = newId;}}
以上实例中public方法是外部类访问该类成员变量的入口
通常情况下,这些方法被称为getter和setter方法
因此,任何要访问类中私有变量的类都要通过这些getter和setter方法
public class RunEncap{public static void main(String args[]){EncapTest encap = new EncapTest();encap.setName("James");encap.setAge(20);encap.setIdNum("12343ms");System.out.print("Name : " + encap.getName()+" Age : "+ encap.getAge());}}
接口
接口(interface),在Java编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明;一个类通过继承接口的方式,从而来继承接口的抽象方法
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念;类描述对象的属性和方法;接口则包含类要实现的方法
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法
接口无法被实例化,但是可以被实现;一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类;另外,在Java中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象
接口与类的相似点
- 一个接口可以有多个方法
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名
- 接口的字节码文件保存在 .class 结尾的文件中
-
抽象类和接口的区别
接口不能用于实例化对象
- 接口没有构造方法
- 接口中所有的方法必须是抽象方法
- 接口不能包含成员变量,除了static和final变量
- 接口不是被类继承了,而是要被类实现
-
接口特性
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式指定为 public abstract (只能是 public abstract,其他修饰符都会报错)
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰符会编译错误)
- 接口中的方法是不能在接口中实现的,只能由接口的类实现接口中的方法
抽象类和接口的区别
- 抽象类中的方法可以由方法体,就是能实现方法的具体功能,但是接口中的方法不行
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的
- 接口中不能含有静态代码块以及静态方法(用static修饰的方法),而抽象类是可以有静态代码块和静态方法
- 一个类只能继承一个抽象类,而一个类可以实现多个接口
接口的声明
接口的声明语法格式为:
接口有以下特性:[修饰符] interface 接口名称 [extends 其他的接口名]{//声明变量//抽象方法}
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字
-
接口的实现
当类实现接口的时候,类要实现接口中所有的方法;否则,类必须声明为抽象的类。
类使用implements关键字实现接口;在类声明中,implements关键字放在class声明后面
实现一个接口的语法,可以使用这个公式:...implements 接口名称[,其他接口名称,其他接口名称...,...]...
重新接口中声明的方法时,需要注意以下规则:
类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性的异常
- 类在重新方法时要保持一致的方法名,并且应该保持相同或相兼容的返回值类型
- 如果实现接口的类是抽象类,那么就没必要实现该接口的方法
在实现接口的时候,也要注意一些规则:
- 一个类可以同时实现多个接口
- 一个类只能继承一个类,但是能实现多个接口
-
接口的继承
一个接口能继承另一个接口,和类之间的继承方式比较相似;接口的继承使用extends关键字,子接口继承父接口的方法。
接口的多继承
在Java中,类的多继承是不合法,但接口允许多继承
在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口,如:public interface Hockey extends Sports, Event
标记接口
最常用的继承接口时没有包含任何方法的接口
标记接口时没有任何方法和属性的接口,它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权
没有任何方法的接口被称为标记接口,标记接口主要用于以下两种目的: 建立一个公共的父接口
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
- 向一个类添加数据类型
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
枚举(enum)
Java枚举时一个特殊的类,一般表示一组常量,比如一年的四个季节,一个年的12个月份,一个星期的7天,方向有东南西北等。
Java枚举类使用enum关键字来定义,各个常量使用逗号来分隔。
例如定义一个颜色的枚举类:
enum Color{RED, GREEN, BLUE;}
使用实例:
enum Color{RED, GREEN, BLUE;}public class Test{// 执行输出结果public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);}}//RED
内部类中使用枚举
枚举类也可以声明在内部类中:
public class Test{enum Color{RED, GREEN, BLUE;}// 执行输出结果public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);}}//RED
每个枚举都是通过Class在内部实现的,且所有的枚举值都是 public static final 的。
以上的枚举类 Color转化在内部类实现:
class Color{public static final Color RED = new Color();public static final Color BLUE = new Color();public static final Color GREEN = new Color();}
迭代枚举元素
可以使用for语句来迭代枚举元素
//实例enum Color{RED, GREEN, BLUE;}public class MyClass {public static void main(String[] args) {for (Color myVar : Color.values()) {System.out.println(myVar);}}}
在switch中使用枚举类
枚举类常应用于switch语句中:
enum Color{RED, GREEN, BLUE;}public class MyClass {public static void main(String[] args) {Color myVar = Color.BLUE;switch(myVar) {case RED:System.out.println("红色");break;case GREEN:System.out.println("绿色");break;case BLUE:System.out.println("蓝色");break;}}}//蓝色
values(),ordinal()和valueOf() 方法
enum定义的枚举类默认继承了 java.lang.Enum类,并实现了 java.lang.Seriablizabel 和 java.lang.Comparable 两个接口。
values(),ordinal()和valueOf()方法位于 java.lang.Enum 类中:
- values() 返回枚举类中所有的值
- ordinal() 返回可以找到每个枚举常量的所有,就像数组索引一样
- valueOf() 方法返回指定字符串值的枚举常量 ```java enum Color { RED, GREEN, BLUE; }
public class Test { public static void main(String[] args) { // 调用 values() Color[] arr = Color.values();
// 迭代枚举for (Color col : arr){// 查看索引System.out.println(col + " at index " + col.ordinal());}// 使用 valueOf() 返回枚举常量,不存在的会报错 IllegalArgumentExceptionSystem.out.println(Color.valueOf("RED"));// System.out.println(Color.valueOf("WHITE"));}
}
//结果为: RED at index 0 GREEN at index 1 BLUE at index 2 RED
<a name="umM70"></a>## 枚举类成员枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。<br />枚举即可以包含具体方法,也可以包含抽象方法;如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它```javaenum Color{RED, GREEN, BLUE;// 构造函数private Color(){System.out.println("Constructor called for : " + this.toString());}public void colorInfo(){System.out.println("Universal Color");}}public class Test{// 输出public static void main(String[] args){Color c1 = Color.RED;System.out.println(c1);c1.colorInfo();}}//结果为Constructor called for : REDConstructor called for : GREENConstructor called for : BLUEREDUniversal Color
