黑马程序员——面向对象最全总结:谁说程序员没有“对象”

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

前言

本文将以我个人对于该部分内容的理解,重新对整个体系进行归纳和划分,以一种不太常见的方式把面向对象的基础内容全部涵盖在内。我把整个面向对象的知识分成两大部分:第一部分是“功能”,第二部分是“关系”。功能部分包括:类的定义、类的初始化、类的实例化、类的修饰、类的扩展几个部分。关系部分包括:实例与类的关系、子类与父类的关系、类与类的关系几个部分。具体内容将在下文详述。
“异常处理”这部分的内容也常常被一些国内外教科书划分在面向对象的章节中,不过由于我认为异常处理部分内容的体系比较有特点,基本上属于自成体系,也有不少内容,将单独再写一篇文章来进行总结和演练。

功能

类的定义:内部类

基本定义与格式

一句话概述:将一个类定义在另一个类里面。

格式1:通用格式

class outerClassName{
	private class innerClassName{
		//body of inner class
	}
}
 
  

格式2:静态内部类

class outerClassName{
	private static class innerClassName{
		//body of inner class
	}
}

格式3:匿名内部类

new ClassOrInterface(){
	//class-body
}

内部类与非内部类的比较

不使用内部类的普通调用
/*
 * notInner想要访问Outer中的field或method,
 * 需要在notInner类中创建一个Outer类的对象
 */
class generalClass
{
	//field
	int num = 3;
	//method
	public void printStar(){//非静态方法
		System.out.println("*");
	}
	public static void printComma(){//静态方法
		System.out.println(",");
	}
}

class notInner{
	//field
	//method
	public static void main(String[] args) {
		generalClass.printComma();//静态方法可以直接用类名调用
		generalClass ot = new generalClass();//非静态方法需要先创建一个实例,在notInner类中创建一个Outer类的对象
		int getNum = ot.num;//notInner类要访问Outer中的field:num,需要在创建实例后才能进行
		ot.printStar();//notInner类要访问Outer中的method:printStar(),需要在创建实例后才能进行
	}

使用内部类时的调用情况
/*
内部类的访问规则:
1,内部类可以直接访问外部类中的成员,包括私有。
	之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式 外部类名.this
2,外部类要访问内部类,必须建立内部类对象。
*/

class Outer//外部类
{
	private String str = "Outer的私有变量";

	
	class Inner//内部类
	//Class Inner还可以被私有修饰 private class Inner,因为它也属于Outer类的成员
	{
		String str = "Inner中的变量";
		void function()
		{
			String str = "function中的变量";
			
			System.out.println("innner :"+str);
			//该语句将会输出:innner :function中的变量
			
			System.out.println("innner :"+this.str);
			//该语句将会输出:innner :Inner中的变量,与Inner.this.str输出结果一致
			
			System.out.println("innner :"+Outer.this.str);
			//Outer后不加this,想要访问Outer中的field会报错,该语句将会输出:innner :Outer的私有变量
			
			System.out.println("innner :"+Inner.this.str);
			//Inner后不加this,想要访问Inner中的field会报错,该语句将会输出:innner :Inner中的变量
		}
	}

	void method()//在innerInOuter中定义的method方法
	{
		Inner in = new Inner();//外部类要访问内部类,必须建立内部类对象。
		in.function();//外部类访问内部类中的function方法
	}
}


class  innerClass
{
	public static void main(String[] args) 
	{
		Outer iio = new Outer();
		iio.method();

		//直接访问内部类中的成员。
		Outer.Inner in = new Outer().new Inner();
		//如果使用Inner in = new Inner();会报错,当内部类被私有时,则不能被访问到
		in.function();
	}
}

静态内部类和非静态内部类的区别

当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限。
class staticInnerClassDemo{
	//在外部其他类中,直接访问非static内部类的methods比较
	Outer.Inner in = new Outer().new Inner();
	in.function();
	
	//在外部其他类中,直接访问static内部类的非静态methods
	new Outer.Inner().function();

	//在外部其他类中,直接访问static内部类的静态methods
	Outer.Inner.function();
}
内部类必须是static的两种情况
1.当内部类中定义了静态成员
2.外部类中的静态方法需要访问内部类
当描述事物时,事物的内部还有事物,该事物用内部类来描述,因为内部事物在使用外部事物的内容。
内部类可以定义在成员位置上,也可以定义在方法内部。定义在成员位置上时,就可以被私有化,也可以被static所修饰。
如果内部类定义在内部,则不能被静态所修饰,也不能私有化。

定义在方法内部的内部类

内部类定义在局部时,
1,不可以被成员修饰符修饰
2,可以直接访问外部类中的成员,因为还持有外部类中的引用。但是不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。

class Outer
{
	int x = 3;

	void method(final int a)
	{
		final int y = 4;
		//内部类定义在局部时,不可以访问它所在的局部中的变量。只能访问被final修饰的局部变量。
		
		//定义在该method内部的内部类
		class Inner
		{
			void function()
			{
				System.out.println(y);
			}
		}
		new Inner().function();//如果不new一个Inner的对象,没有这句语句,将不会运行。没对象,不运行
		
	}
}


class  InnerClassDemo
{
	public static void main(String[] args) 
	{
		Outer out = new Outer();
		out.method(7);//出栈后释放
		out.method(8);//又进栈后开辟新的a
	}
}

匿名内部类

匿名内部类:
1,匿名内部类其实就是内部类的简写格式。
2,定义匿名内部类的前提:内部类必须是继承一个类或者实现接口。
3,匿名内部类的格式:  new 父类或者接口(){定义子类的内容}
4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。 可以理解为带内容的对象。
5,匿名内部类中定义的方法最好不要超过3个。

class Outer
{
	int x = 3;

	
	//没有使用匿名内部类时的情况:以下
	/*
	class Inner extends AbsDemo
	{
		int num = 90;
		void show()
		{
			System.out.println("show :"+num);
		}

	}
	
	public void function()
	{
		new Inner.show();
	}
	*/
	
	//使用匿名内部类时的情况:以下
	public void function()
	{
		
		//匿名内部类,直接用AbsDemo创建对象,后面还带着代码块{}
		new AbsDemo()//创建的是AbsDemo的匿名子对象
		{
			void show(){
				System.out.println("show :"+num);
			}
		}.show();//注意:分号在这里,整个代码块是一个整体,是AbsDemo的子类对象,最后是方法调用。匿名对象对方法只能调用一次
		
	}
}

类的初始化

1.构造函数

对象一建立就会调用与之对应的构造函数。
构造函数的作用:可以用于给对象进行初始化。
构造函数的小细节:
1.当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。
2.当在类中自定义了构造函数后,默认的构造函数就没有了
一个对象建立,构造函数只运行一次。
而一般方法可以被该对象调用多次。
什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。

/*
 程序输出结果:
 学号:003	姓名:张飞
 学号:004	姓名:关羽
 学号:001	姓名:刘备	职务:班长
 学号:002	姓名:诸葛亮	职务:学习委员 
 */
public class constructorDem {
	public static void main(String[] args) {
		Students stu1 = new Students("003","张飞");//1号张飞,学号如果设置成int,则001将会输出1,所以要用String来存学号
		Students stu2 = new Students("004","关羽");//2号关羽
		StuLeader sl1 = new StuLeader("001","刘备","班长");//班长刘备
		StuLeader sl2 = new StuLeader("002","诸葛亮","学习委员");//学习委员诸葛亮
		
		//打印输出,否则控制台没有显示结果
		System.out.println(stu1);
		System.out.println(stu2);
		System.out.println(sl1);
		System.out.println(sl2);
	}
}



class Students{
	private String number;//学号
	private String name;//姓名
	
	
	//构造函数
	public Students(String number, String name){
		this.setNumber(number);
		this.setName(name);
	}

	
	//学号的getter和setter
	public String getNumber() {
		return number;
	}


	public void setNumber(String number) {
		this.number = number;
	}

	//姓名的getter和setter
	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public String toString(){
		return "学号:" + number +  "\t"  + "姓名:" + name ;
	}
}	

class StuLeader extends Students{//学生干部是一个特殊的学生的类,继承自学生,多一个职务的属性
	
	//Implicit super constructor Students() is undefined for default constructor. Must define an explicit constructor
	//因为在父类Students中没有定义默认的构造器,所以必须要在该类中定义一个显式的构造器。因为在子类隐式的构造器开头隐含一个super();
	
	private String duty;
	public StuLeader(String number, String name, String duty){
		super(number,name);//不能写成super(int number,String name);
		this.duty = duty;
	}
	@Override
	public String toString(){
		return super.toString() + "\t" + "职务:" + duty;//如果学号不对齐,使用tab键并调用super的时候会发生一些问题
	}
}

	

2.构造代码块

作用:给对象进行初始化
对象一建立就运行,并优先于构造函数运行
和构造函数的区别:
构造代码块是给所有对象进行统一初始化,
而构造函数是给对应的对象初始化。
构造代码快中定义的是不同对象共性的初始化内容。
/*
运用构造代码块,小孩不管有名字没名字,生下来都会哭了
小孩生下来哭了
小孩:Bob

小孩生下来哭了

 */
class Baby
{
	private String name;
	
	{
		System.out.println("小孩生下来哭了");
	}
	

	Baby()
	{

	}
	
	Baby(String name)
	{
		this.name =name;
	}
	
	
	@Override //重写的toString不能放在构造函数中间,还需要在主函数中打印输出对象
	public String toString(){
	return "小孩:" + name + "\n";
	}

}

class  ConDem2
{
	public static void main(String[] args) 
	{
		//有名字的小孩
		Baby b = new Baby("Bob");
		System.out.println(b);
		
		//没名字的小孩
		new Baby();
	}
}


类的实例化

匿名对象

匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成
匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。
匿名对象调用field没意义
class Car{
	String name = "奥迪";
	String color = "红色的";
	
	void run(){
		System.out.println(color + name + "在欢乐地奔跑");
	} 
}

class AnonymousInstance {
	public static void main(String[] args) {
		
		//正常创建对象的方法
		Car redAudi = new Car();
		redAudi.run();
		
		//匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成
		new Car().name = "被黑客改了名字的奥迪";
		new Car().run();//即使改了名字,这句话输出的结果还是:红色的奥迪在欢乐地奔跑
		//因为new Car().run();在创建之前,new Car().name = "被黑客改了名字的小土车";在内存中就已经被销毁了,匿名对象调用field没意义
		
		changeCar(redAudi);//程序将会输出:黑色的奥迪在欢乐地奔跑
	}
	
	//匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。
	public static void changeCar(Car c){
		c.color = "黑色的";
		c.run();
	}
}

Person p = new Person("张飞",20);这句话都做了什么

1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
3,在堆内存中开辟空间,分配内存地址。
4,在堆内存中建立对象的特有属性。并进行默认初始化。
5,对属性进行显示初始化。
6,对对象进行构造代码块初始化。
7,对对象进行对应的构造函数初始化。
8,将内存地址付给栈内存中的p变量。

单例设计模式

单例设计模式的作用:解决一个类在内存只存在一个对象。

保证对象唯一的目的:
1,为避免其他程序过多建立该类对象,先禁止其他程序建立该类对象
2,又必须u让其他程序可以访问到该类对象,只好在本类中,自定义一个对象
3,为方便其他程序对自定义对象的访问,可以对外提供一些访问方式。

创建单例设计模式的步骤:
1,将构造函数私有化
2,在类中创建一个本类对象
3,提供一个可以获取到该对象的方法

1.饿汉式(Eager)

这个是先初始化对象,称为:饿汉式。
class Singleton{
	//1,将构造函数私有化。
	private  Singleton(){
		
	}	
	//2,在类中创建一个本类对象
	private static Singleton s = new Singleton();//静态方法只能访问静态变量,故此处只能是static。类和对象都可以属于field
	
	//3,提供一个可以获取到该对象的方法
	public static  Singleton getInstance()
	//需要通过该方法才能访问对象,但是方法被调用只能通过对象、类名两种方式
	//现在没有对象,故需要用类名调用,必须是static方法
	{
		return s;
	}
}


class SingleDemo {
	Singleton sgt = Singleton.getInstance();//不能再通过new来创建对象了,只能通过调用Singleton中的getInstance方法
}

2.懒汉式(Lazy)

对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式。
Single类进内存,对象还没有存在,只有当调用了getInstance方法时,才建立对象。
class Singleton2
{
	
	
	//1,将构造函数私有化。
	private Singleton2(){}
	
	//2,在类中创建一个本类对象
	private static Singleton2 s = null;
	
	//3,提供一个可以获取到该对象的方法
	public static Singleton2 getInstance()
	{
		if(s==null)
		{
			synchronized(Singleton2.class)//多线程部分的内容
			{				
				if(s==null)
					s = new Singleton2();
			}
		}
		return s;
	}
}


类的修饰符

1.Static

Static一言以蔽之:是属于类自带的
用法:是一个修饰符,用于修饰成员(成员变量,成员函数).
当成员被静态修饰后,就多了一个调用方式,除了可以被对象调用外,
还可以直接被类名调用。类名.静态成员。

static特点:
1,随着类的加载而加载,随着类的消失而消失。说明它的生命周期最长。
2,优先于的对象存在(静态先存在,对象后存在)
3,被所有对象所共享
4,可以直接被类名所调用

实例变量和类变量的区别:
1,存放位置。
类变量:随着类的加载而存在于方法区中
实例变量:随着对象的建立而存在于堆内存中
2,生命周期:
类变量:生命周期随着类的消失而消失
实例变量:生命周期随着对象的消失而消失

静态使用注意事项:
1,静态方法只能访问静态成员(非静态方法既可以访问静态也可以访问非静态)
2,静态方法中不可以定义this,super关键字。(因为静态优先于对象存在。所以静态方法中不可以出现this)
3,主函数是静态的。

静态的利弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
可以直接被类名调用。
弊端:生命周期过长。
 访问出现局限性。(静态虽好,只能访问静态。)


class Bike{
	public static String color = "red";
	
	public static void run(){
		System.out.println("自行车跑啊跑");
	}
}

public class StaticDemo {
	public static void main(String[] args) {
		
		//普通调用方式调用Bike的field中的color
		Bike giant = new Bike();
		System.out.println(giant.color);
		
		//类变量的另一种调用方式
		System.out.println(Bike.color);//效果与第一种调用方式相同
		
		//调用类变量的方法
		Bike.run();
	}
}

2.Static Initializer:静态代码块

静态代码块。
格式:
static
{
静态代码块中的执行语句。
}
特点:随着类的加载而执行,只执行一次,并优先于主函数。
用于给类进行初始化的。
即使构造代码块定义在静态代码块之前,还是先执行静态代码块的内容
class StaticCode
{
	int num = 9;
	
	//构造函数
	StaticCode()
	{
		System.out.println("构造函数的内容被打印了");
	}
	
	
	
	//构造代码块
	{
		System.out.println("构造代码块的内容被打印了 " + this.num);
	}
	
	//静态代码块
	static
	{
		System.out.println("静态代码块的内容被打印了");
	}
	


	//构造函数超载
	StaticCode(int x)
	{
		System.out.println("构造函数超载的内容被打印了");
	}
	
	//静态方法
	public static void show()
	{
		System.out.println("show run");
	}
}


class StaitcInitializer 
{
	static
	{
		//System.out.println("b");
	}
	public static void main(String[] args) 
	{
		new StaticCode(4);//因为没有创建过StaticCode的对象,"构造函数的内容被打印了"这行不会被打印
	}
}

/*
 程序输出结果
 
静态代码块的内容被打印了:即使构造代码块定义在静态代码块之前,还是先执行静态代码块的内容
构造代码块的内容被打印了 9
构造函数超载的内容被打印了

*/

类的扩展

1.抽象类

抽象类的特点:
1,抽象方法一定在抽象类中。
2,抽象方法和抽象类都必须被abstract关键字修饰。
3,抽象类不可以用new创建对象。因为调用抽象方法没意义。
4,抽象类中的抽象方法要被使用,必须由子类复写起 所有的抽象方法后(非抽象方法可以不用实现),建立子类对象调用。
5,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

abstract 关键字旁边,和哪些关键字不能共存?
final:被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
private: 抽象类中的私有的抽象方法,不被子类所知,就无法被复写。而抽象方法出现的就是需要被复写。
static:如果static可以修饰抽象方法,那么连对象都省了,直接类名调用就可以了。可是抽象方法运行没意义。

模版方法:
在定义功能时,功能的一部分是确定的,但是有一部分是不确定,而确定的部分在使用不确定的部分,
那么这时就将不确定的部分暴露出去。由该类的子类去完成。

abstract class GetTime//把GetTime的功能定义成抽象类,模板设计模式
{
	public final void getTime()//之所以要使用final关键字,是为了不让getTime被复写
	{
		long start = System.currentTimeMillis();

		runcode();

		long end = System.currentTimeMillis();

		System.out.println("毫秒:"+(end-start));
	}
	public abstract void runcode();//要求子类复写该抽象方法,以得到不同代码相对应的运行时间

}


class SubTime extends GetTime
{

	public void runcode()//开始复写父类方法
	{
		
		for(int x=0; x<4000; x++)
		{
			System.out.print(x);
		}
	}
}


class  AbstractDemo
{
	public static void main(String[] args) 
	{
		SubTime gt = new SubTime();
		gt.getTime();
	}

2.接口

接口定义时,格式特点:
1,接口中常见定义:常量,抽象方法。
2,接口中的成员都有固定修饰符。
常量:public static final
方法:public abstract 

注意:
1,接口中的成员都是public的。
2,接口是不可以创建对象的,因为有抽象方法。
3,接口需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。否则子类是一个抽象类。
4,接口可以被类多实现,也是对多继承不支持的转换形式。java支持多实现。
abstract class Student
{
	abstract void study();
	void sleep()
	{
		System.out.println("sleep");
	}

}

interface Smoking//抽烟不是学生都具有的行为,定义为接口
{
	void smoke();
}

class ZhangSan extends Student implements Smoking
{
	void study(){}
	public void smoke(){}
}

关系

子父类关系特点

1.子父类中变量的特点

如果子类中出现非私有的同名成员变量时,
子类要访问本类中的变量,用this。this:代表的是本类对象的引用。
子类要访问父类中的同名变量,用super。super:代表的是父类对象的引用。
class Fu 
{
	public String str = "爹的成员";
}

class Zi extends Fu
{
	String str = "儿的方法";//一般不会这么定义,因为子类可以从父类中获取
	void show()
	{
		System.out.println(str);//儿的成员
		System.out.println(this.str);//儿的成员
		System.out.println(super.str);//爹的成员
	}
}


class ExtendsDemo {
	public static void main(String[] args) 
	{
		Zi z = new Zi();
		z.show();
	}
}

2.子父类中方法的特点

当子类出现和父类一模一样的函数时,子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。
这种情况是函数的另一个特性:Override(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,
这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。
覆盖:
1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
2,静态只能覆盖静态。
class Father
{
	void methods()
	{
		System.out.println("爹的方法");
	}
}

class Son extends Father
{
	void methods()
	{
		System.out.println("儿的方法");
	}
}

class ExtendsDemo2
{
	public static void main(String[] args) 
	{
		Son z = new Son();
		z.methods();//儿的方法
	}
}

3.子父类中构造函数的特点

在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句 super();
super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();
为什么子类一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。
注意:super语句一定定义在子类构造函数的第一行。

多态(Polymorphism)

1.多态的概念

多态:可以理解为事物存在的多种体现形态。

多态的体现
父类的引用指向了自己的子类对象。
父类的引用也可以接收自己的子类对象。
多态的前提
必须是类与类之间有关系。要么继承,要么实现。
通常还有一个前提:存在覆盖。
多态的利弊
利:多态的出现大大的提高程序的扩展性。
弊:只能使用父类的引用访问父类中的成员。
多态的应用

2.多态引入的目的

最原始版本:为了让Cat类中的不同实例调用eat方法,需要反复创建该类的实例
abstract class Animal
{
	abstract void eat();

}

class Cat extends Animal
{
	public void eat()
	{
		System.out.println("吃鱼");
	}
	public void catchMouse()
	{
		System.out.println("抓老鼠");
	}
}


class Dog extends Animal
{
	public void eat()
	{
		System.out.println("吃骨头");
	}
	public void watchHome()
	{
		System.out.println("看家");
	}
}




public class PolyDemo {
	public static void main(String[] args) {
		Cat heimao = new Cat();
		heimao.eat();
		Cat lanmao = new Cat();
		lanmao.eat();
	}
}

 
    

改进版本:将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法,提高代码复用性
public class PolyDemo {
	public static void main(String[] args) {
		Cat heimao = new Cat();
		//heimao.eat();
		getEatmethod(heimao);
		
		Cat lanmao = new Cat();
		//lanmao.eat();
		getEatmethod(lanmao);
	}
	
	public static void getEatmethod(Cat c){//将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法
		c.eat();
	}
}

改进版本2:将Dog类作为参数,传递给一个专门用来调用Dog类中的eat()方法的方法,运用方法的重载
public class PolyDemo {
	public static void main(String[] args) {
		Cat heimao = new Cat();
		getEatmethod(heimao);
		
		Dog xiaohuang = new Dog();
		getEatmethod(xiaohuang);//或者直接getEatmethod(new Dog());
	}
	
	
	public static void getEatmethod(Cat c){//将cat类作为参数,传递给一个专门用来调用Cat类中的eat()方法的方法
		c.eat();
	}
	
	public static void getEatmethod(Dog d){//将Dog类作为参数,传递给一个专门用来调用Dog类中的eat()方法的方法,运用方法的重载
		d.eat();
	}
}

改进版本3:利用多态性,直接运用父类
public class PolyDemo {
	public static void main(String[] args) {
		getEatmethod(new Cat());
		getEatmethod(new Dog());
	}
	
	
	public static void getEatmethod(Animal a){//直接传入父类即可
		//Animal a = new Cat();
		//Animal a = new Dog();
		a.eat();
	}
	

3.多态的转型

向上转型:类型提升,引用数据类型的提升

Animal a = new Cat();//类型提升。 向上转型。猫的类型提升为Animal

向下转型:猫提升为Animal后,失去了猫的特有方法,如果此时想要再调用猫的特有方法时,如何操作?
Cat c = (Cat)a;//强制将父类的引用a。转成子类类型Cat。向下转型。此时就可以使用c.catchMouse()了

错误代码:把动物转成猫的类型

Animal a = new Animal();
//千万不要出现这样的操作,就是将父类对象转成子类类型。
//我们能转换的是父类引用指向了自己的子类对象时,该引用可以被提升,也可以被强制转换。
//多态自始至终都是子类对象在做着变化。
Cat c = (Cat)a;

 
    

4. instanceof

假如没有instanceof,出现类型转换异常时的情况:
public class PolyDemo2 {
	public static void main(String[] args) {
		method(new Cat());
		method(new Dog());//传入method后,等于是把狗强制转换成了猫,会报类型转换异常
	}
	
	public static void method(Animal a){
		a.eat();
		Cat c = (Cat)a;//向下转型,转型后就可以抓老鼠了
		c.catchMouse();
	}
}
/*
吃鱼
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to Cat
抓老鼠
吃骨头
	at PolyDemo2.method(PolyDemo2.java:40)
	at PolyDemo2.main(PolyDemo2.java:35)
*/

有instanceof 时的处理方法
public static void method(Animal a)//Animal a = new Cat();
	{
		a.eat();
		/*小细节:不要把父类写在上面
		if(a instanceof Animal)
		{
			System.out.println("haha");
		}
		else 
		*/
		if(a instanceof Cat)
		{
			Cat c = (Cat)a;
			c.catchMouse();
		}
		else if(a instanceof Dog)
		{
			Dog c = (Dog)a;
			c.kanJia();
		}


		/*
		instanceof : 用于判断对象的类型。 对象 intanceof 类型(类类型 接口类型)  
		*/
	
	}

5.多态中成员的特点

在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。
在多态中,成员变量的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。
在多态中,静态成员函数的特点:
无论编译和运行,都参考做左边。

Object类:所有类的父类

Object:是所有对象的直接后者间接父类。该类中定义的肯定是所有对象都具备的功能。
Object类中已经提供了对对象是否相同的比较方法。如果自定义类中也有比较相同的功能,没有必要重新定义。
只要沿袭父类中的功能,建立自己特有比较内容即可。这就是覆盖。

class Demo{ //extends Object
	
}

class Person{
	
}	

class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo();
		Demo d2 = new Demo();
		Demo d3 = d1;
		System.out.println(d1.equals(d2));//输出:false
		System.out.println(d3.equals(d1));//输出:true
		System.out.println(d1.toString());//输出:Demo@659e0bfd
		System.out.println(d2.toString());//输出:Demo@2a139a55
		System.out.println(d1);//输出语句打印对象时,会自动调用对象的toString方法。打印对象的字符串表现形式。输出:Demo@659e0bfd


		Class c = d1.getClass();

		System.out.println(c.getName());//输出:Demo
		System.out.println(c.getName()+"@"+Integer.toHexString(d1.hashCode()));//输出:Demo@659e0bfd
		
		//比较Person对象
		Person p1 = new Person();
		Person p2 = new Person();
		System.out.println("Person: " + p1.equals(p2));//输出:Person: false
	}
}


自己定义一个比较的方法,版本1:
class Demo{ //extends Object
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	
	public boolean compare(Demo d){
		return this.num == d.num;
	}

}



class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo(4);
		Demo d2 = new Demo(3);
		Demo d3 = new Demo(4);
		System.out.println(d1.compare(d2));//输出:false
		System.out.println(d1.compare(d3));//输出:true
		

	}
}

自己定义一个比较的方法,版本2:
父类Object中已经提供了比较的方法,不用再自己写compare方法
可以自行复写Object中的equals方法
class Demo{ //extends Object
	private int num;
	Demo(int num)
	{
		this.num = num;
	}
	
	public boolean equals(Object obj){//Object obj = new Demo();
		Demo d = (Demo)obj; 
		return this.num == d.num;
		//直接访问obj.num将会报错,找不到Object类中的num,想使用子类的东西,需要向上转型,然后访问d.num
	}

}



class ObjectDemo 
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo(4);
		Demo d2 = new Demo(3);
		Demo d3 = new Demo(4);
		System.out.println(d1.equals(d2));//输出:false
		System.out.println(d1.equals(d3));//输出:true
	}
}











你可能感兴趣的:(JAVA)