第10章 接口.继承.多态
封装的概念
一些变量用private封装起来,用get.set方法来获取.
public class Lady {
private String name;
private int age;//age的初值0
public void setName(String n) {
name = n;
}
public void setAge(int a) {
if(a>100 || a<1) {
System.out.println("您的输入有误");
}else {
age = a;
}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
//测试类
public class LadyDemo {
public static void main(String[] args) {
Lady lady = new Lady();
lady.setAge(30);
lady.setName("东方姑娘");
System.out.println(lady.getAge());
System.out.println(lady.getName());
}
}
10.1 类的继承
用一句话来概括继承是一个子类使用extends关键字来继承父类的一些非私有的成员变量或者成员方法
1.继承在面向对象开发思想中是一个非常重要的概念,它可以使程序架构具有一定的弹性在程序维护中复用一些一定定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性.
2.继承,基本思想是基于某个父类的扩展,指定出一个新的子类,子类可以继承父类原有的属性和方法,或者直接重写父类中的某些方法.
在Java中使用extends关键字来标识两个类的继承关系.
class Test{
public Test(){//构造方法
}
protected void doSomething(){//成员发法
}
protected Test doIt(){//方法返回值类型为Test类型
return new Test();
}
}
class Test2 extend Test{
public Test2(){//构造方法
super();//调用父类构造方法
super.doSomething();//调用父类成员方法
}
public void doSomething(){//新增方法
}
public void doSomething(){//重写父类方法
}
prtected Test2 doIt(){
return new Test2();
}
}
- 在上述实例代码中,Test2类继承Test类.在子类中可以连同初始化父类构造方法来完成子类的初始化操作,既可以在子类的构造方法中使用super()语句调用父类的构造方法,也可以在子类中使用super关键字调用父类的成员方法,或成员变量.
- 但是子类没有权限调用父类中被修饰为private的方法,只可以调用父类中修饰为public或protected的成员方法.也可以定义一些新方法,例如子类中doSomething()方法.
- 继承并不只是扩展父类的功能,还可以重写父类的成员方法.重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或者是修改成员方法的返回值类型.上述例子,子类中的doSomething()方法,除了重写方法的实现内容之外,还将方法权限修饰符修改为public.
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值
,方法名称,参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构.当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围的改变.
子类重写父类的方法还可以修改方法的返回值类型.例如上述例子父类的doIt方法的返回值类型为Test,而子类中的doIt方法的返回值类型为Test2,子类重写了父类的doIt方法.这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2正是Test类的子类.
super关键字
1.在构造方法中,
super([参数列表]);
没有参数的情况下,会执行父类无参构造来构建父类对象
有参数情况下,会执行父类的有参构造来构建父类对象
还可以用super.成员(方法和变量)
2.在子类的成员方法中
可以使用super.成员变量或者成员方法,来调用父类的成员变量或方法.但是在成员方法中不能使用super()
在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的.两者的区别后者来自于外部,而前者来自于子类的对象内部.当实例化子类对象时,父类对象也相应被实例化.换句话说在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法,下面给出实例
class Parent { // 父类
Parent() {
System.out.println("调用父类的parent()构造方法");
}
}
class SubParent extends Parent { // 继承Parent类
SubParent() {
System.out.println("调用子类的SubParent()构造方法");
}
}
public class Subroutine extends SubParent { // 继承SubParent类
Subroutine() {
System.out.println("调用子类的Subroutine()构造方法");
}
public static void main(String[] args) {
Subroutine s = new Subroutine(); // 实例化子类对象
}
}
实例化子类对象时首先要实例化父类对象,然后实例化子类对象,所以在构造子类构造方法访问父类的构造方法之前,父类已经完成实例化操作
在实例化对象时.父类的无参构造方法将被调用,但有参构造方法并不能被自动调用,只能依赖于super关键字显式地调用父类构造方法
10.2 Object类
final关键字,final关键字修饰的方法,在子类中不能被重写.
用final修饰的类,不能再有子类继承它.不能被继承
用final修饰的基本变量是常量,值不能被改变.
Final关键字修饰的引用类型变量,地址值不能被改变.
基本数据类类型存的是数值本身,而引用类型变量在内存放的数据引用,并不是数据本身,引用类型变量是以间接方式获取数据.引用类型变量都属于对象类型.如:数组,类,字符串等都属于引用类型变量.所以引用类型变量里面存放的是数据的地址.
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中,所有的类都直接或间接继承了Java.lang.Object类.
Object类中主要包括clone(),finalize(),equals(),toString()等方法,其中最常用的两个方法为equals()和toString()方法.由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法.
Object类中的getClass(),notify(),notifyAll(),wait()等方法不能被重写,因为这些方法被定义为final类型.
断点调试
(1.)hashCode()
返回该对象的哈希码值,同一个对象的hashCode相同,不同对象的不同.
student2 = student; student对象的地址传给了student2,所以两个对象的hashCode值相同.
(2.) toString():
如果不重写,输出的是get.Class().getName()+”@”+Integer.toHexString(hashCode())
System.out.println(对象);//实际上调用的是
System.out.println(对象.toString());
(3.)getClass()
(4.)equals()
用于判断两个对象是否相等
默认:判断他们的地址是否相同,如果相同返回true,否则false
重写:判断两个对象的成员变量是否相同,如果是返回true,否则false.
如果该方法被重写,还需要重写hashCode方法
//equals()
class V { // 自定义类V
}
public class OverWriteEquals {
public static void main(String[] args) {
String s1 = "123"; // 实例化两个对象,内容相同
String s2 = "123";
System.out.println(s1.equals(s2)); // 使用equals()方法调用
V v1 = new V(); // 实例化两个V类对象
V v2 = new V();
System.out.println(v1.equals(v2)); // 使用equals()方法比较v1与v2对象
}
}
上述代码中,默认情况下,equals()判断两个对象是否相同,上述s1和s2两个对象比较为true,因为String这个类这个类型重写了equals(0方法,实现了两个字符串比较内容是否相同,默认情况下,是用来比较这两个对象的引用(v1跟v2)是否指向了同一个对象,很明显两个都是通过new创建了两个新的对象
10.3 对象类型的转换
四边形类:抽象类型
平行四边形类:较为具体的类
class Quadrangle { // 四边形类
public static void draw(Quadrangle q) { // 四边形类中的方法
// SomeSentence
}
}
public class Parallelogram extends Quadrangle { // 平行四边形类,继承了四边形类
public static void main(String args[]) {
Parallelogram p = new Parallelogram(); // 实例化平行四边形类对象引用
draw(p); // 调用父类方法
}
}
平行四边形是一种类型的四边形,所以可以将平行四边形类的对象看作是一个四边形类的对象,这就相当于”Quadrangle obj = new Parallelogram();”,也就是把子类对象赋值给父类类型的 变量.这种技术称为”向上转型”,向上转型是一个较为具体的类到较为抽象的类的转换,所以它是安全的.
向下转型:
class Quadrangle { // 四边形类
public static void draw(Quadrangle q) { // 四边形类中的方法
// SomeSentence
}
}
public class Parallelogram extends Quadrangle { // 平行四边形类,继承了四边形类
public static void main(String args[]) {
draw(new Parallelogram);
//将平行四边形类对象看作是四边形对象,称为向上转型操作
Quadrangle q = new Parallelogram;
//Parallelogram p = q;
//将父类对象赋予子类对象,这种写法错误
//将父类对象赋予子类对象,并强制转换为子类类型,这种写法是正确的额
Parallelogram p = (Parallelogram)q;
}
}
10.4 使用instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例.
class Quadrangle {
public static void draw(Quadrangle q) {
// SomeSentence
}
}
class Square extends Quadrangle {
// SomeSentence
}
class Anything {
// SomeSentence
}
public class Parallelogram extends Quadrangle {
public static void main(String args[]) {
Quadrangle q = new Quadrangle(); // 实例化父类对象
// 判断父类对象是否为Parallelogram子类的一个实例
if (q instanceof Parallelogram) {
Parallelogram p = (Parallelogram) q; // 向下转型操作
}
// 判断父类对象是否为Parallelogram子类的一个实例
if (q instanceof Square) {
Square s = (Square) q; // 进行向下转型操作
}
// 由于q对象不为Anything类的对象,所以这条语句是错误的
// System.out.println(q instanceof Anything);
}
}
10.5 方法的重载
public class OverLoadTest {
public static int add(int a, int b) { // 定义一个方法
return a + b;
}
// 定义与第一个方法相同名称、参数类型不同的方法
public static double add(double a, double b) {
return a + b;
}
public static int add(int a) { // 定义与第一个方法参数个数不同的方法
return a;
}
public static int add(int a, double b) { // 定义一个成员方法
return 1;
}
// 这个方法与前一个方法参数次序不同
public static int add(double a, int b) {
return 1;
}
public static void main(String args[]) {
System.out.println("调用add(int,int)方法:" + add(1, 2));
System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));
System.out.println("调用add(int)方法:" + add(1));
}
}
虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置.
通过图10.10所示的构成方法重载的条件,可以总结出编译器是利用方法名,方法各参数类型和参数个数以及参数的顺序来确定类中的方法是否唯一.
确定两个方法是否具有重载关系时,会想到定义不长参数方法.
public static int add (int...a){
int s = 0;
for(int i = 0;i<a.length;i++){
s += a[i];
return s;
}
}
//
public class OverLoadTest2 {
public static int add(int a, int b) {
return a + b;
}
public static double add(double a, double b) {
return a + b;
}
public static int add(int a) {
return 1;
}
public static int add(int a, double b) {
return 1;
}
public static int add(double a, int b) {
return 1;
}
public static int add(int... a) { // 定义不定长参数方法
int s = 0;
for (int i = 0; i < a.length; i++)
// 根据参数个数做循环操作
s += a[i]; // 将每个参数累加
return s; // 将计算结果返回
}
public static void main(String args[]) {
System.out.println("调用add(int,int)方法:" + add(1, 2));
System.out.println("调用add(double,double)方法:" + add(2.1, 3.3));
System.out.println("调用add(int)方法:" + add(1));
// 调用不定长参数方法
System.out.println("调用不定长参数方法:" + add(1,2, 3,4, 5,6, 7, 8, 9));
System.out.println("调用不定长参数方法:" + add(2,3,4));
}
}
10.6 多态
在子类重写的方法上面可以标一个@Override说明是一个重写的方法.
Father f = new son(); 父类的引用指向子类的对象
多态的特点:
- 有继承
- 有方法的重写
- 父类引用指向子类对象
来看Father f = new son();有继承,son继承了Father,play()方法重写了,Father f = new son();这就是父类的引用指向了子类的对象.
调用重写过的方法,实际可以调用子类方法
调用父类的方法
不能调用子类特有的方法
多态的方法的调用
main(..){
doSth(new Son());
doSth(new Daughter());
}
public static void doSth(Father father) {
father.play();
}
}
向下转型
注意:
Father f = new Son();
Son son = (Son)f;
son.game();//f本来就是son,可以强转成Son
//但是如果想把Son强转成Daughter就会报错,因为实际上还是一个Son
// Daughter d = (Daughter)f;
// d.drink();
北风网 第四章抽象类和接口
1.抽象类和抽象方法
抽象 abstract
abstract不可以修饰成员变量;
抽象类:
概念:用abstract修饰的类都是抽象类
语法:
public abstract class Person{}
1.抽象类不能被实例化
2.抽象类的非私有成员可以被访问
3.子类要实现从父类继承过来的抽象方法(body(方法体)),除非这个方法在子类中也是抽象的
4.凡是含有抽象方法的类都必须是抽象类
5.抽象类也有构造方法,并且也调用
6.abstract关键字和static关键字不能共存
7.抽象类引用作为参数,实际需要的是子类对象
public class PersonDemo {
public static void main(String[] args) {
// Person person = new Person();
// person.eat();
Zhangfei zhangfei = new Zhangfei();
zhangfei.eat();
zhangfei.sleep();
zhangfei.show();
show(zhangfei);
}
/*
* 抽象类引用作为参数,实际需要的是子类对象
*/
public static void show(Person person) {
person.eat();
}
}
抽象类2还差一点.
抽象方法:
概念:用abstract修饰的方法都是抽象方法
语法:public abstract void eat();
3.接口(interface)
接口是实现,类是继承.
语法:
interface 接口名{
}
接口的实现:
语法:
class 类名 implements 接口名1,接口名2{
}
接口特性:
- 在接口中,所有的方法都是抽象类,而且都是public abstract修饰的
- 接口的实现类,必须实现接口中所有的抽象方法,如果不实现接口中的抽象方法,需要将此方法也设置为抽象类.
- 接口可以多实现
- 一个类可以在继承一个父类的同时实现多个接口
- 接口中的变量默认是public final static(要用最好要用接口名.变量名来调用)修饰的.
- 如果接口类型作为参数,那么实际要传递的是其实现类对象.
//6.
public class FatherDemo{
main(){
doSth(son);
}
public static void doSth(Smoker smoker){
smoker.smoke();
}
}
public abstract class Swiman implements Swimmer{}
//这个Swimmer是一种技能,被继承不合适