封装、继承和多态

封装、继承和多态

继承

继承的基本思想是基于某个父类进行拓展,得到一个新的子类。子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。

package week1;
/**
 * @author tao
 * 在继承的机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的
 * 两者的区别在于后者来自外部,而前者来自子类对象的内部
 * 当实例化子类对象时,父类对象也被相应的实例化,换句话说,在实例化子类对象时,Java编译器会在子类的构造方法中自动调用父类的无参构造方法
 */

/*  eg:下面的例子将会出现——未定义隐式超构造函数a( ),必须显示调用另一个构造函数的报错
 * 此处需要注意的是公共类型public必须在自己的文件中定义,即用public修饰的主类的类名必须与自己的文件名相同
 * 一个文件中只能有一个public修饰的主类
class a{
	a(int a){
		System.out.println("调用a类的构造方法!");
	}
}

public class Demo extends a {
	Demo(){
		System.out.println("调用Demo类的构造方法!");
	}
	因为a类中定义了一个有参的构造函数,如果a类中未定义无参的构造方法,那么在Demo类构造无参的构造方法时系统将会报错——未定义隐式超构造函数a( ),必须显示调用另一个有参构造函数
	此时有两种解决方法
	1、在a类中再构造一个无参的构造方法
	2、将Demo类中的构造方法也改写为有参的形式,如下
	public class Demo extends a {
	Demo(int a){
		super(a);
		System.out.println("调用Demo类的构造方法!");
	}
  }
  
}
总结:
1、在实例化子类对象时,父类无参构造方法将会被自动调用;有参构造方法不能被自动调用,用户只能使用super关键字显示的调用父类的构造方法。
2、实例化子类对象时首先要实例化父类对象,然后再实例化子类对象,所以在子类构造方法访问父类的构造方法之前,父类已经完成实例化操作。
3、如果父类的构造方法是无参的,那么子类的构造方法可以是无参也可以是有参,且系统会自动调用父类的无参构造方法。
4、如果父类的构造方法是有参的,那么子类的构造方法必须是有参的,且必须使用super关键字对父类的有参构造方法进行显示调用(这条语句必须放在第一句,否则会报错);或再在父类中构造一个无参的构造方法,让系统自动调用父类的无参构造方法。
5、如果使用finalize()方法对对象进行清理,需要确保子类finalize()方法的最后一个动作是调用父类的finalize()方法,以保证当垃圾回收对象占用内存时,对象的所有部分都能被正常终止。
*/


class Test {
	public Test() {		//构造方法
		//SomeSentence
	}
	protected void doSomething() {		//成员方法
		//SomeSentence
	}
	protected Test dolt() {		//方法返回值类型为Test类型
		return new Test();
	}
	
	
	
	
 class Test2 extends Test{		//继承父类Test类
	 public Test2() {		//构造方法
		 /**
		  * 子类中可以连同初始化父类构造方法来完成子类的初始化操作
		  * 既可以在子类的构造方法中使用super()语句调用父类的构造方法
		  * 也可以在子类中使用super关键字调用父类的成员方法等,但需要注意的是,super()语句一定得放在第一句,否则会报错
		  * 但是子类没有权限调用父类中被修饰为private的方法,只可以调用父类中修饰为public和protected的成员方法
		  */
		 super();			//调用父类构造方法
		 super.doSomething();		//调用父类成员方法
	 }
	 public void doSomethingnew() {		//在子类中定义的新方法doSomethingnew()
		//在子类中可以定义一些新方法
		//SomeSentence
	 }
	 public void doSomething() {		//重写父类方法
		 /**
		  * 重写还可以称为覆盖,就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型
		  * 或是修改成员方法的返回值类型(这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2类正好是Test类的子类)
		  * 子类Test2中的doSomething()方法,除了重写方法的实现内容之外,还将方法的修饰权限符改为public
		  * 当重写父类方法时,修改方法的权限修饰只能从小的范围到大的范围改变
		  * 如,父类中的doSomething()方法的权限修饰符为protected
		  * 继承后子类中的方法doSomething()的权限修饰符只能修改为public,不能修改为private
		  * 在继承中还有一种特殊的重写方法,子类和父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊的重写方式称为重构
		  */

		 //SomeSentence
	 }
	 protected Test dolt() {	//重写父类方法,方法返回值类型为Test2类型
		 //这种重写方式需要遵循一个原则,即重写的返回值类型必须是父类中同一方法返回值类型的子类,而Test2类正好是Test类的子类
		 return new Test2();
	 }
 }
}

Object类

Object类(java.lang )
java.lang.Object


1、类 Object 是类层次结构的根类。每个类都使用 Object 作为超类(最顶层的父类)。所有对象(包括数组)都实现这个类的方法。
2、在Java中所有的类都会直接或间接的继承这个Object类,Object类是一个比较特殊的类,它是所有类的父类,是Java中最基层的一个类。
3public class person{   }
     public class person extends Object{   }
由于所有的类都是Object类的子类,所以在定义的时候可以自动忽略extends Object,因此上面和下面这两种写法的效果其实是一样的。

一、Object类的三个常用方法

1getClass(  )	返回对象执行时的Class实例
程序实例:
public class Test {
	public static void main(String[] args) {
		Object[] arr = new Object[4];
		
		arr[0] = new Object();
		arr[1] = new String("字符串对象");
		arr[2] = new Integer(10);
		arr[3] = new Test();
		
		for(Object obj:arr) {
			System.out.println(obj.getClass());
		}
	}
}
输出结果:
class java.lang.Object
class java.lang.String
class java.lang.Integer
class week1.Test

2toString(   )	将对象返回为字符串形式,如果子类不重写这个方法的话,它则会默认返回类名+@+十六进制的一个哈希值
程序实例:
public class Test {
	public static void main(String[] args) {
		Object[] arr = new Object[4];
		
		arr[0] = new Object();
		arr[1] = new String("字符串对象");
		arr[2] = new Integer(10);
		arr[3] = new Test();
		
		for(Object obj:arr) {
			System.out.println(obj.toString());
		}
	}
}
输出结果:
java.lang.Object@15db9742
字符串对象
10
week1.Test@6d06d69c 
( 其中Integer类和String类重写了toString(   )方法,使其的返回结果不调用Object类的toString方法,即return  super.toString() )
public class Test {
	public static void main(String[] args) {
		Object[] arr = new Object[4];
		
		arr[0] = new Object();
		arr[1] = new String("字符串对象");
		arr[2] = new Integer(10);
		arr[3] = new Test();
		
		for(Object obj:arr) {
			System.out.println(obj.toString());
		//.toString(  )是可以去掉的,如果我们直接输出Object类的对象的话,会自动调用它的toString(  )方法
		}
	}
	//输入toString之后按AIt+/即可重写父类的方法
	@Override
	public String toString() {
		// TODO 自动生成的方法存根
		return "我是Test类";
		//不重写之前是  return super.toString();
	}
}
重写Test类的toString(  )方法之后,返回结果为"我是Test类"

3equals(  )	比较两个对象是否相等
程序实例:
public class Test {
	public static void main(String[] args) {
		Object[] arr = new Object[4];
		
		arr[0] = new Object();
		arr[1] = new String("字符串对象");
		arr[2] = new Integer(10);
		arr[3] = new Test();
		
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(arr[0].equals(arr[3]));
		System.out.println(arr[3].equals(new Test()));
		System.out.println(str1.equals(str2));
		//Object类中的equals( )方法比较的是两个对象的地址是否相等,相当于等号
		//而在String类中的equals( )方法比较的是两个String对象的值是否相等,而不是地址
	}
}
输出结果:
false
false
true
Object类中的equals( )方法比较的是两个对象的地址是否相等,相当于等号。但是在我们的实际情况中,我们比较两个对象是否相等的时候可能比的并不是它的内存地址。
例如我们这里有两份简历,这两份简历上写的身份证号是同一个身份证号,那么我们实际上就可以认为这两份简历是同一个人了。
注意:
“==”号和Object类中的equals( )方法一样,比较的是两个对象的地址,而不是两个对象的value值
String类的equals( )方法比较的则是两个对象的value值,需要注意的是,String类的equals方法只能比较两个String类对象的value值是否相同,不能对不同类的对象进行比较
eg:
public class Test {
	public static void main(String[] args) {
		String s = "123456";
		int i = 123456;
		String s2 = "123456";
		System.out.println(s.equals(i));
		System.out.println(s.equals(s2));
	}
}
输出结果:
false
true

程序实例:
public class Test {
	public static void main(String[] args) {
		Person p1 = new Person();
		Person p2 = new Person();
		Person p3 = new Person();
		
		p1.name = "小明";
		p1.id = "123";
		
		p2.name = "小红";
		p2.id = "123";
		
		p3.name = "小明";
		p3.id = "123";
		
		System.out.println(p1.equals(p2));
		System.out.println(p2.equals(p3));
		System.out.println(p1.equals(p3));
		System.out.println(p1 == p3);
	}
}

class Person {
	String name;
	String id;
	@Override
	public boolean equals(Object arg0) {
		// TODO 自动生成的方法存根
		Person p = (Person)arg0;
		boolean b1 = this.name.equals(p.name);
		boolean b2 = this.id.equals(p.id);
		return b1 && b2;
	}
}
输出结果:
false
false
true
false

对象类型的转换(类的上下转型)

类的上下转型

定义:
定义两个类,一个是小狗类,一个是动物类,当我们看见一个小狗的时候我们可以说,这个小狗它是一个动物,但是,如果我们看到任何一个动物的时候都直接说它是一个小狗,显然是不可以的。
这个动物类就是小狗类的父类,我们可以认为子类的对象它同时也是一个父类的对象,小狗它既是小狗它也是动物;但是父类的对象就不一定是子类的对象了,我们看见一个动物,它是狗吗?不一定,也有可能是小鸟;子类的对象转为父类的对象就是向上的转型,而父类的对象转为子类的对象就是向下的转型。
在父类转为子类的时候我们一定要注意,这种转换可能是失败的!

一、类的向上转型
1、语法:Parents object = new Child( );
用父类Parents类声明一个对象object,用子类实例化一个对象Child

示例:Person tom = new Student( );
理解:首先Person表明这是一个人,然后它叫tom,而后面实例化的是一个学生类的对象,说明tom是一个学生。
程序实例:
第一个源程序文件:
public class Person {
	public Person(String name) {
		System.out.println("你好,我叫"+name);
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		
	}

}

class Student extends Person{

	public Student(String name) {
		super(name);
	}
	
}

第二个源程序文件:
public class Demo{
	public static void main(String[] args) {
		Person tom = new Student("tom");
		Person Jerry = new Student("Jerry");
	}
}
输出结果:
你好,我叫tom
你好,我叫Jerry

二、类的向下转型
1、语法:Parents p = new Parents( );
	Child c = (Child)p;   必须使用强制转换
示例:Person tom = new Person( );    这里有一个人,我们叫他tom
          Doctor dr_tom = (Doctor)tom;    我们这里需要医生!快叫tom来当医生!

程序实例:
第一个源程序文件:
public class Person {
	public Person(String name) {
		System.out.println("你好,我叫"+name);
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		
	}

}

class Doctor extends Person{

	public Doctor(String name) {
		super(name);
	}
	
}
第二个源程序文件:
public class Demo{
	public static void main(String[] args) {
		Person jack = new Person("jack");
		
		Doctor dr = (Doctor)jack;
	}
}
输出结果:
你好,我叫jack
此时虽然正常输出了,但系统会抛出一个异常
Exception in thread "main" java.lang.ClassCastException: week1.Person cannot be cast to week1.Doctor
	at week1.Demo.main(Demo.java:8)
因为jack它只是一个普通人,并不是一个医生,如果将第二个源程序文件中的代码改为
Person jack = new Doctor("jack");
Doctor dr = (Doctor)jack;
这样系统就不会抛出异常了;

三、instanceof关键字
(instanceof是Java语言的关键字,在Java语言中的关键字都为小写)
1、用于判断某一个实例对象是否属于某一个类(需要注意的是,两个没有任何继承关系的类是不能用instanceof关键字进行比较的,否则系统将会报错),或判断是否一个类实现了某个接口;
2、语法:boolean result = child instanceof parents;
3、使用instanceof操作符的表达式返回值为布尔值,如果返回值为true,说明child对象为parents的实例对象;如果返回值为false,说明child对象不是parents的实例化对象;
4、当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例(例如Person jack = new Doctor("jack"),此时父类对象就是子类对象的实例;而Person jack = new Person("jack"),此时父类对象就不是子类对象的实例;),就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是利用instanceof关键字判断父类对象是否为子类对象的实例。

程序实例:
第一个源程序文件:
public class Person {
	public Person(String name) {
		System.out.println("你好,我叫"+name);
	}
	public static void main(String[] args) {
		// TODO 自动生成的方法存根
	
	}

}

class Doctor extends Person{

	public Doctor(String name) {
		super(name);
	}
	
}
第二个源程序文件:
public class Demo{
	public static void main(String[] args) {
		Person jack = new Doctor("jack");
		System.out.println(jack instanceof Doctor);
		Doctor dr = (Doctor)jack;
	}
}
输出结果:
你好,我叫jack
true
不会发生ClassCastException异常
如果第二个源程序文件改写为:
public class Demo{
	public static void main(String[] args) {
		Person jack = new Person("jack");
		System.out.println(jack instanceof Doctor);
		Doctor dr = (Doctor)jack;
	}
}
输出结果:
你好,我叫jack
false
会发生ClassCastException异常

多态

多态

1、利用多态可以使程序具有良好的拓展性,并可以对所有类对象进行通用的处理。

2、使用多态节省了开发时间和维护时间,因为程序员无需在所有的子类中定义执行相同功能的方法,避免了大量重复代码的编写。同时,只要实例化一个继承父类的子类对象,即可调用相应的方法,这里只要维护父类中的这个方法即可。

3、多态最直观的特征就是同一个变量,同一个方法,执行出不同的结果。
例:
动物		>——>		移动
鲤鱼		>——>		游过去
老鹰		>——>		飞过去
斑马		>——>		跑过去

一、多态的效果
程序实例:
class Animal{
	void move(){
		
	}
}

class Fish extends Animal{
	void move(){
		System.out.println("游泳");
	}
}

class Hawk extends Animal{
	void move(){
		System.out.println("飞翔");
	}
}
public class Test{
	public static void main(String[] args) {
		Animal a = new Fish();
		a.move();
		
		a = new Hawk();
		a.move();
	}
}
这段代码中始终使a一个变量(动物)在执行move( )方法(移动),它按照不同的子类来进行实例化的话(向上转型),move( )方法执行的结果是完全不同的。
输出结果:
游泳
飞翔

方法的重载

方法的重载

我们曾学过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一个名称。所以希望以不同的方式来实例化对象,就需要使用多个不同的构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到方法重载。虽然方法重载起源于构造方法,但它也可以应用到其他方法中。

一、重载的定义
方法的重载就是在同一个类中允许存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。
以下是三种不同的重载方式
1、参数类型不同,构成重载
2、参数个数不同,构成重载
3、参数顺序不同,构成重载
程序实例:
public class Test {
	//定义一个加法方法
	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 100;
	}
	//这个方法与前一个方法参数次序不同
	public static int add(double a,int b) {
		return -100;
	}
	public static void main(String[] args) {
		System.out.println("调用add(int a,int b)方法:"+add(1,2));
		System.out.println("调用add(double a,double b)方法:"+add(2.1,3.3));
		System.out.println("调用add(int a)方法:"+add(1));
		System.out.println("调用add(int a,double b)方法:"+add(1,1.1));
		System.out.println("调用add(double a,int b)方法:"+add(2.2,9));
	}
}
输出结果:
调用add(int a,int b)方法:3
调用add(double a,double b)方法:5.4
调用add(int a)方法:1
调用add(int a,double b)方法:100
调用add(double a,int b)方法:-100

注意:
虽然在方法重载中可以使两个方法的返回类型不同,但只有返回类型不同并不足以区分两个方法的重载,还需要通过参数的个数以及参数的类型来设置。

二、
1、编译器是利用方法名、方法各参数类型和参数的个数以及参数的顺序来确定类中的方法是否唯一。方法的重载使得方法以统一的名称被管理,使程序代码有条理。
2、在谈到参数个数可以确定两个方法是否具有重载关系时,会想到定义不定长参数方法。
public static int add(int...a) {		//定义不定长参数方法
		int s=0;
		for(int i=0;i<a.length;i++)
		s+=a[i];		//将参数累加操作
		return s;		//将结果返回
	}
上述方法又是一个add( )重载方法,它与前面提到的重载方法的不同之处在于该方法为不定长参数方法。
不定长方法的语法如下:
返回值 方法名(参数数据类型...参数名称)
在参数列表使用“...”形式定义不定长参数,其实这个不定长参数a就是一个数组,编译器会将(int...a)这种形式看作是(int[]a),所以在add( )方法体做累加操作时使用到了for循环语句,在循环中是根据数组a的长度作为循环条件的,最后将结果返回。
程序实例:
public class Test{
	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(1,2,3,4,5,6,7,8,9));
		System.out.println("调用不定长参数方法:"+add(1));
	}
}
输出结果:
调用不定长参数方法:45
调用不定长参数方法:1
从上面的程序实例中可以看出,定义不定长参数依然可以作为add( )方法的重载方法,由于它的参数是不定长的,所以满足根据参数个数区分重载的条件

接口

接口

1、接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中所有的方法都没有方法体。

2、在接口中,方法必须被定义为publicabstract形式,其他权限修饰符不被Java编译器所认可。
或者说,即使不将该方法声明为public,它也是publicpublic interface drawTest{
          void draw( );		//即使我没有在代码中对接口中的draw方法进行public和abstract修饰,但它依然是被public和abstract修饰的
}

3、在接口中定义的任何字段都自动是staticfinal的。

一、接口的语法
接口使用interface关键字进行定义,语法如下:
public interface DrawTest{
          void draw( );		//接口内的方法,省略abstract关键字
}
1public: 接口可以像类一样被权限修饰符修饰,但public关键字仅限用于接口在与其同名的文件中被定义
这与主类的概念有异曲同工之妙,一个源文件中可以有多个类,但只能有一个被public修饰的主类;同样的,一个源程序文件中可以有多个接口,但只能有一个被public修饰的主接口;

2interface: 定义接口的关键字

3、DrawTest:  接口名称
一个类实现一个接口,可以使用implements关键字,代码如下:
public class Parallelogram extends Quadrangle Implements drawTest{
	...//
}
程序实例:
接口:
public interface DrawInterface {
	void draw();
}
源程序文件一:
public class Qua implements DrawInterface{	//四边形简称

	public void draw() {
		System.out.println("绘制四边形");
	}	
	
}

class square implements DrawInterface{

	public void draw() {
		System.out.println("绘制一个正方形");
	}
	
	
}
源程序文件二:
public class Demo{
	public static void main(String[] args) {
		DrawInterface d1 = new Qua();	//接口可以进行向上转型
		DrawInterface d2 = new square();
		d1.draw();
		d2.draw();
	}
}
输出结果:
绘制四边形
绘制一个正方形

接口可以进行向上转型,其实在Java中无论是将一个向上转型为父类对象还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都是没有问题的;




二、接口与继承
1、Java中不允许出现多重继承,但使用接口就可以实现多重继承。
一个类可以同时实现多个接口,因此可以将所有需要继承的接口放置在inplements关键字后面并使用逗号隔开。但这可能会在一个类中产生庞大的代码量,因为继承一个接口时需要实现接口中的所有的方法。

多重继承的语法如下:
class 类名 implements 接口一,接口二,接口三,....,接口n

2、在定义一个接口时,使该接口继承另外一个接口(与多重继承一个原理)
注意:接口继承接口使用的是extends关键字而不是implements关键字
但一个类实现一个或多个接口时就必须使用implements关键字了
程序实例:
interface intf1{
}
interface intf2 extends intf1{
}

3、一个类可以边继承边实现接口
interface intf1{
}
public class Red extends Color implements intf1{
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值