面向对象

一、面向对象的概念

   1、理解面向对象:

面向对象是相对面向过程而言,面向对象和面向过程都是一种思想。面向过程强调的是功能行为;面向对象将功能封装进对象,强调具备了功能的对象。面向对象是基于面向过程的。将复杂的问题简单化。

   2、面向对象的特点:

是一种符合人们思考习惯的思想,可以将复杂的事情简单化。将程序员从执行者转换成了指挥者。完成需求时:先要去找具有所需的功能的对象来用。如果该对象不存在,那么创建一个具有所需功能的对象。这样简化开发并提高复用。

面向对象举例:

比如,买电脑。直接找个懂的人去帮忙买,于是买电脑的所有过程都交给这个人去做。因此,买电脑这件事由复杂变简单,自己由执行者变成指挥者。

比如,软件公司招程序员。(1)先说面向对象的特点(是一种符合人们思考习惯的思想,可以将复杂的事情简单化。将我们从执行者转换成了指挥者。。。。。。等等)(2)举例说明,最好结合场景,也要强调对象具备相应功能。(其实面试官就是利用面向对象的思想来思考问题,公司现有招人需求,是因为有可能用户量比较大,代表贵公司也是蒸蒸日上的发展。您需要招一些具备专业知识和能力的人来帮您完成工作,我就是对象,具备专业编程能力,其实您就是指挥我做事情,您在使用我的专业编程功能为公司创造价值。)

   3、面向对象开发,设计,特征

开发的过程:其实就是不断的创建对象,使用对象,指挥对象做事情。

设计的过程:其实就是在管理和维护对象之间的关系。

面向对象的三大特征:封装(encapsulation)、继承(inheritance)、多态(polymorphism)

 

二、类与对象的关系

类:就是对现实生活中事物的描述。比较抽象。

对象:就是这类事物实实在在的个体。

映射到Java中,描述就是class定义的类。具体对象就是对应Java在堆内存中用new建立的实体。

对象中包含很多数据,数据多了要用实体存储,像数组一样,对象也是用来存储数据的。凡是存储多个数据都叫实体,存储在堆内存中。


图纸就是类;汽车就是存在堆内存中的对象。

描述汽车(描述事物就是描述事物的属性和行为)

代码如下:

class Car{
	//描述颜色
	String color="red";//显式初始化值
	//描述轮胎数量
	int num=4;
	//运行行为(对应的是方法)
	void run(){
		System.out.println(color+","+num);
	}}

综上所述:属性对应类中变量,行为对应类中的函数(方法)。其实定义类就是在描述事物,就是在定义属性和行为。属性和行为共同成为类中成员(成员变量、成员方法)。

 

关于成员变量和局部变量:

成员变量:

成员变量定义在类中,在整个类中都可以被访问。

成员变量随着对象的建立而建立,存在于对象所在的堆内存中。

成员变量有默认初始化值,因此不初始化也可以参与运算。(在类中有赋值时,就称为显式初始化值)。

局部变量:

局部变量只定义在局部范围内,如:函数内,语句内等。

局部变量存在于栈内存中。

作用的范围结束,变量空间会自动释放。

局部变量没有默认初始化值,不初始化就不能使用。

内存分析:

实例1

class Text {
	public static void main(String[] args){
		//生产汽车就是在Java中通过new来在堆内存中产生一个实体
		Car c=new Car();//c就是一个类类型变量,类类型变量指向对象
		c.color="blue";//改变颜色
		c.run();//结果是blue,4
		Car c1=new Car();//又创建了一个对象
		c1.run();//结果是red,4
		}}
class Car{
	//描述颜色
	String color="red";//显式初始化值
	//描述轮胎数量
	int num=4;
	//运行行为(对应的是方法)
	void run(){
		System.out.println(color+","+num);
	}
}

首先加载主函数,栈内存底部开辟了一个空间,定义了一个变量c,堆内存中开辟了一个空间,即对象。该对象中有成员变量color和num,并且都有显式初始化值。对象的地址值赋给栈内存中的c,c就指向了该对象。当执行c.color=”blue”;时,堆内存中颜色改成blue,改变的是实体的变量值,不是类的变量值。

如图:


当又创建了一个对象时,堆内存中有两个实体。如图:


//实例2:
Car c=new Car();
	   c.color="blue";//改变颜色
	   Car c1=c;//又创建了一个对象
	c1.num=9;
c.run();//结果是blue,9  

这个例子就是栈内存中的两个变量指向了堆内存中同一个实体。如图:


匿名对象

new Car().num=5;//这三句都是执行下句代码前,该句在堆内存
new Car().color="绿色";//中产生的实体变成垃圾了没有指向
new Car().run();//就成了垃圾,于是内存中有仨垃圾

只要new就会产生新的实体。

综上所述,匿名对象调用属性无意义,调用方法有意义。

使用方法:1.当对象的方法只调用一次时,可以用匿名对象来完成,这样写比较简化。如果对一个对象进行多个成员调用,那就必须给这个对象起名了。

          2.可以将匿名对象作为实际参数进行传递。

public static void main(String[] args){
		Car c=new Car();
		show(c);//结果是黑色,2
	}
	public static void show(Car c){
		c.num=2;
		c.color="黑色";
		c.run();
	}

首先加载主函数,在栈内存底部开辟一块空间,定义变量c.在堆内存中开辟了

一个空间,即对象。该对象中有成员变量color和num,并且都有显式初始化值。

对象的地址值赋给栈内存中的c,c就指向了该对象。

如图:


然后调用show函数,此时会加载show函数,在栈内存中开辟空间,show函数有个形式参数c,于是也会在内存中定义c。然后主函数中c的值会传给show函数中的c,于是show函数中的c也指向了堆内存中的对象。如图:


然后就执行show函数的操作,改变对象中的值。当show函数执行完毕,就会释放内存。

如图:


此时,这个实体还不是垃圾,因为还有指向。如果想要该对象变垃圾,可以加一句代码:

C=null。如果,main函数也执行完毕,那就垃圾了。

 

用匿名对象做实际参数:

public static void main(String[] args){
		show(new Car());//结果是黑色,2
	}
	public static void show(Car c){
		c.num=2;
		c.color="黑色";
		c.run();
	}

首先会加载主函数,然后调用show函数,在堆中分配一个空间建立一个对象。该空间地址值直接传给show函数的形式参数c,于是c指向了给对象。因为有指向,所以该对象不是垃圾。当show方法执行完毕,释放栈内存空间,由于堆中的对象没有指向,所以变垃圾。

整个过程如图所示:


三、封装(Encapsulation)

封装:是指隐藏对象的属性和实现细节,仅对外提供 公共访问方式。(函数就是最小的封装体)

好处:

将变化隔离。

便于使用。

提高重用性。

提高安全性。

封装原则:

将不需要对外提供的内容都隐藏起来。

把属性都隐藏,提供公共方法对其访问。

private关键字(私有,与public相反)

私有,权限修饰符。用于修饰类中的成员(成员变量、成员函数)。私有只在本类中有效,即本类以外的一切都不能访问。

将成员变量私有化,类以外即使建立了对象也不能访问。但是对外提供对应的set ,get 方法对其进行访问。提高对数据访问的安全性。如setAge,getAge。一个成员变量通常对应两个访问方式:setter和getter

注意:封装不是私有,私有仅仅是封装的一种表现形式。之所以对外提供访问方式,就是因为可以在访问方式中加入逻辑判断语句,对访问的数据进行操作,提高代码的健壮性。

 

四、构造函数

   特点:1.函数名与类名相同

2.不用定义返回值类型(与void不一样,void也是一种返回类型)

3.不可以写return语句

作用:给对象进行初始化。

对象一建立就会调用与之对应的构造函数。多个构造函数是以重载的形式存在的。

注意:当一个类中没有定义构造函数时,系统会默认给该类加一个空参数的构造函数,方便对象的初始化,否则对象是建立不出来的。当在类中建立了构造函数时,默认的就没了。构造函数是可以被私有化的,私有后,相应的对象就不能创建,因为不能初始化。

class Text {
	public static void main(String[] args){
		Person p=new Person();//运行结果person run
	}
}
class Person{
	Person(){
		System.out.println("person run");
	}
}
//但是,假如代码是这样:
class Text {
	public static void main(String[] args){
		Person p=new Person();//编译错误。因为p找不到空参数的构
	                            //造函数初始化
        Person q=new Person(23);//编译错误,因为相应构造函数被私有
	}
}
class Person{
	private Person(int a){
		System.out.println("person run"+a);
	}

构造函数和一般函数的区分:

   1.构造函数和一般函数的写法不同。

   2.构造函数是在对象一建立就运行,给对象初始化。

     一般函数是对象调用才执行,是给对象添加对象具备的功能。

  3.一个对象建立,构造函数只运行一次。一般函数可以多次调用。

什么时候定义构造函数:

   当分析事物时,该事物存在具备一些特性或行为,那就将这些内容定义在构造函数中。

 

构造代码快(据说开发用不到,面试会用到)

  作用:给对象进行初始化。

  运行:对象一建立就运行,而且优先于构造函数。

  与构造函数的区别:构造代码块是将所有的对象进行统一初始化;

                    构造函数是给对应的对象进行初始化。

 

 实例:

class Text {
	public static void main(String[] args){
		Person p=new Person();
		Person q=new Person("ni");
	}
}/*运行结果是person run
1
person run
2
*/
class Person{
	{System.out.println("person run");//这就是构造代码块
	}
	Person(){
		System.out.println(1);
	}
	Person(String a){
		System.out.println(2);
	}
	

五、this关键字

 this看上去是用于区分局部变量和成员变量同名的情况。This代表本类对象,代表它所在函所在类的引用。简单说,哪个对象在调用this所在函数,this就带表哪个对象。

应用:当定义类中功能时,该函数内部要调用到函数的对象时,这时用this来表示这个对象。但凡本类功能内部使用到了本类对象,都用this表示。

用于构造函数间相互调用时格式为:this(参数);只能定义在构造函数第一行,因为初始化要先执行。

class Text {
	public static void main(String[] args){
		Person p=new Person();
		Person q=new Person(3);
		p.setAge(4);
		q.setAge(4);
		System.out.println(p.compare(q));
		}
	}
//运行结果为:出生了
出生了
3岁了
true
<p align="left"><strong><span style="color:#7F0055;">class</span></strong> Person{</p><p align="left">   <strong><span style="color:#7F0055;">private</span> <span style="color:#7F0055;">int</span></strong> <span style="color:#0000C0;">age</span>;</p><p align="left">   <strong><span style="color:#7F0055;">void</span></strong> setAge(<strong><span style="color:#7F0055;">int</span></strong> age){</p><p align="left">      <span style="color:#3F7F5F;">//age=age;</span><span style="color:#3F7F5F;">这样是不能给对象的</span><span style="color:#3F7F5F;">age</span><span style="color:#3F7F5F;">赋值的</span></p><p align="left">      <strong><span style="color:#7F0055;">this</span></strong>.<span style="color:#0000C0;">age</span>=age;<span style="color:#3F7F5F;">//</span><span style="color:#3F7F5F;">谁调用该函数,</span><span style="color:#3F7F5F;">this</span><span style="color:#3F7F5F;">就指谁</span></p><p align="left">      }</p><p align="left">     <strong><span style="color:#7F0055;">int</span></strong> getAge(){</p><p align="left">      <strong><span style="color:#7F0055;">return</span></strong> <span style="color:#0000C0;">age</span>;</p><p align="left">      }</p><p align="left">   Person(){</p><p align="left">      <span style="color:#3F7F5F;">//this(0);</span><span style="color:#3F7F5F;">这里如果不注释掉,俩构造函数相互调用会有死循环</span></p><p align="left">      System.<em><span style="color:#0000C0;">out</span></em>.println(<span style="color:#2A00FF;">"</span><span style="color:#2A00FF;">出生了</span><span style="color:#2A00FF;">"</span>);</p><p align="left">      }</p><p align="left">   Person(<strong><span style="color:#7F0055;">int</span></strong> age){</p><p align="left">      <strong><span style="color:#7F0055;">this</span></strong>();<span style="color:#3F7F5F;">//</span><span style="color:#3F7F5F;">调用空参数的构造函数</span></p><p align="left">      System.<em><span style="color:#0000C0;">out</span></em>.println(age+<span style="color:#2A00FF;">"</span><span style="color:#2A00FF;">岁了</span><span style="color:#2A00FF;">"</span>);</p><p align="left">      }</p><p align="left">   <strong><span style="color:#7F0055;">boolean</span></strong> compare(Person p)<span style="color:#3F7F5F;">//</span><span style="color:#3F7F5F;">比较跟别人的年龄是否相同</span></p><p align="left">   {</p><p align="left">      <strong><span style="color:#7F0055;">return</span> <span style="color:#7F0055;">this</span></strong>.<span style="color:#0000C0;">age</span>==p.<span style="color:#0000C0;">age</span>;</p><p align="left">   }</p><p>   }</p>

六、static 关键字(静态)

   用法:是一个修饰符,用于修饰成员(成员变量、成员函数),不能修饰局部。被static修饰的成员被引用时不在堆内存中。被修饰的成员被共享,每个对象都能访问。(特有数据要随着对象存储)

       当成员被静态修饰后,就多了一种调用方式,除了可以被对象调用外,还可以直接被类名调用,格式为:类名.静态成员

 

*内存中还有一块方法区(也可以叫共享区或者数据区),类中的方法、共享数据都存在这个区域。

 

   特点:(1)、随着类的加载而加载,随着类的消失而消失,说明他的生命周期最长。(类被使用到时会加载进内存,被static修饰的变量在方法区开辟空间,随着类的消失而消失)

        (2)、优先于对象存在,静态先存在,对象后存在。

        (3)、被所有对象共享。

       (4)、可以直接被类名访问。

  *成员变量也叫实例变量;静态的成员变量也叫类变量。

   实例变量和类变量的区别;

  (1)、存放位置:类变量随着类的加载存放在方法区

                 实例变量随着对象的建立存在与栈内存。

   (2)、生命周期:类变量的生命周期最长,随着类的消失而消失

                实例变量随着对象的消失而消失。

静态的使用注意事项:

(1)、静态方法只能访问静态成员。

     非静态方法可以访问静态成员也可以访问非静态成员。

 (2)、静态方法中不可以定义this、super关键字。

     因为静态优先于对象存在,所以不能用this。

 (3)、主函数是静态的

静态的利弊:

   利:对对象的共享数据进行单独空间的存储,节省空间,可直接被类名调用。

   弊:生命周期过长。访问出现局限性,只能访问静态。

 

Main函数:是一个特殊的函数,作为程序入口,可以被JVM调用。

    main函数的定义:

       public:代表该函数访问权限是最大的。

       Static:代表主函数随着类的加载就已经存在了。

       Void:代表主函数没有具体的返回值。

       Main:不是关键字,但是是一个特殊的单词,可以被JVM识别。

     (String[] args):函数的参数,参数类型是数组,数组中元素的类型是字符串类型。

主函数格式是固定的,JVM识别,识别函数名是main,函数列表是String[]args

主函数的格式中唯一可以改变的就是args,这本身就是一个参数名。

代码一:
class Text {
	public static void main(String[] args){
		main(2);//调用函数,运行结果是2
System.out.println(args);//结果是
// [Ljava.lang.String;@60e128
		System.out.println(args.length);//结果是0
	}
public static void main(int x){//重载了,但是这个不被JVM识别
	System.out.println(x); 
}}
/*第二个结果说明JVM调用主函数的时候传入的是字符串类型数组,第三个结果说明数组长度为0*/

/*主函数这个数组我们可以传值进去,1、javac命令启动编译器。Java启动JVM,我们要在JVM启动时传参数。在DOS命令行中的命令格式是Java 类名 要传的数据
代码为:DOS命令行的命令为Java Text ss dd*/
class Text {
	public static void main(String[] args){
				System.out.println(args);//结果是[Ljava.lang.String;@60e128
		System.out.println(args.length);//结果是2
		System.out.println(args[1]);}//结果是dd
  <p><strong><span style="color:#7F0055;">//2</span><span style="color:#7F0055;">、还可以用调用函数</span></strong></p><p align="left"><strong><span style="color:#7F0055;">class</span></strong> Text {</p><p align="left">   <strong><span style="color:#7F0055;">public</span> <span style="color:#7F0055;">static</span> <span style="color:#7F0055;">void</span></strong> main(String[] args){</p><p align="left">      String[] arr={<span style="color:#2A00FF;">"hello"</span>,<span style="color:#2A00FF;">"haha"</span>,<span style="color:#2A00FF;">"nihao"</span>,<span style="color:#2A00FF;">"</span><span style="color:#2A00FF;">再见</span><span style="color:#2A00FF;">"</span>};</p><p>      Text2.<em>main</em>(arr);<span style="color:#3F7F5F;">//<span style="color:#3F7F5F;">//</span><a target=_blank target="_blank" name="OLE_LINK2"></a><a target=_blank target="_blank" name="OLE_LINK1"><span style="color:#3F7F5F;">静态方法可以直接用类名调</span></a><span style="color:#3F7F5F;">用</span></span></p><p><span style="font-family: Arial, Helvetica, sans-serif;">}}</span></p><p align="left"><strong><span style="color:#7F0055;">class</span></strong> Text2{</p><p align="left">   <strong><span style="color:#7F0055;">public</span> <span style="color:#7F0055;">static</span> <span style="color:#7F0055;">void</span></strong> main(String[] args){</p><p align="left">      <strong><span style="color:#7F0055;">for</span></strong>(<strong><span style="color:#7F0055;">int</span></strong> i=0;i<args.<span style="color:#0000C0;">length</span>-1;i++)</p><p align="left">         System.<em><span style="color:#0000C0;">out</span></em>.print(args[i]+<span style="color:#2A00FF;">","</span>); </p><p align="left">      System.<em><span style="color:#0000C0;">out</span></em>.println(args[args.<span style="color:#0000C0;">length</span>-1]); </p><p align="left">   }}</p><p align="left"><span style="color:#3F7F5F;">//</span><span style="color:#3F7F5F;">运行结果是</span><span style="color:#3F7F5F;">hello,haha,nihao,</span><span style="color:#3F7F5F;">再见</span></p>

什么时候使用静态:

  两方面:因为静态修饰的有成员变量和成员函数

          当对象中出现了共享数据时,该数据被静态所修饰,对象中的特有数据定义成非静态。

          当功能内部没有访问到非静态数据,那该功能可以定义成静态。

静态的应用:

    每一个应用程序中都有共性的功能,可以把这些抽取,独立封装,便于复用。

 

*程序说明书(当两个class文件都用到且不在同一目录,要配置classpath。当一个类Text中调用到另一个类Text2,且两个类分别在两个Java文件中,那么不管这俩文件在不在一个目录,都会去找文件名跟类名一致的Java文件进行编译)

 Java的说明书通过文档注释来完成。Public修饰的功能都用文档注释,这样就可以被文档注释工具提取。要想生成帮助文档,该类必须被public修饰。私有和无修饰符的功能的文档也不会被提取。

生成帮助文档在DOS中的命令是javadoc –d 存放目录 (–auther –version作者和版本,可以不加)文件名.java 

 

注意:一个类中假如没有定义构造函数,那就默认会有一个空参数的构造函数,该空参数构造函数的权限和所属类一致。如果类被public修饰,那么默认空参数构造函数也被public修饰,该构造函数的权限随着类的权限的改变而改变。

 

静态代码块:

 格式:static

     {

      //静态代码块中的执行语句

}

 特点:随着类的加载而执行,只执行一次,并优先于主函数,用于给类初始化

//代码一:
class Text {
	public static void main(String[] args){
		System.out.println(2);	
	}
	static//代码顺序次于主函数,但从执行结果看,static优先于主函数执行
    {
		 System.out.print("静态代码块中");
		 System.out.print("------");
}}//结果是  静态代码块中------2

//代码二:
class Text {
	public static void main(String[] args){
		Person s=null;//代码中不加下面一行时,无执行结果
		s=new Person();//代码中加上该行时执行结果为:静态代码块
		}}

class Person{
	 static
     {
		 System.out.println("静态代码块");
}}//综上,当定义一个变量时,并没有加载类,只有new时,才建立

//代码三:
class Text {
	 static
     { System.out.print("1");
		 System.out.print("--");}
	public static void main(String[] args){
		Person s=new Person();
	}
	 static
     {System.out.print("2");
		 System.out.print("---");
}}//执行结果:1--2---静态代码块----
class Person{
	 static
     {
		 System.out.print("静态代码块");
		 System.out.print("----");
}}

对象初始化过程:

  1.因为new一个对象时会用到该对象所属类的class文件,所以会先把class文件加载到内存中。

  2.执行该类中的static代码块,给类初始化。

  3.在堆内存中开辟空间,分配内存地址

  4.在堆内存中建立对象的特有属性,并进行默认初始化

  5.对属性进行显示初始化。

  6.对对象进行构造代码块初始化。

  7.对对象进行相应的构造函数初始化。

  8.内存地址赋值给栈内存中的变量

class Text {
	public static void main(String[] args){
		Person.speak();
		Person s=new Person(45);
		s.speak();
	}}
class Person{
	private int age;
	private static String add="China";
	{ //System.out.print(age);//必须可以使用非静态变量
       //speak();
	    System.out.print("构造代码块");
	    System.out.print("----");}
	public void setAge(int age){
		this.age=age;}
	public int getAge(){
		return age;}
	Person(){
		System.out.print("空参数构造函数"+”,”+"年龄"+age);
		System.out.print("----");}
	Person(int age){
		this();
		System.out.print("年龄"+age);
		System.out.print("----");}
	 static
     { System.out.print("静态代码块");
		 System.out.print("----");
     }
	 public static void speak(){
		 System.out.print("静态方法"+add);
		 System.out.print("----"); 
	 }}

上述代码运行结果是:静态代码块----静态方法China----构造代码块----空参数构造函数,年龄0----年龄45----静态方法China----

首先:Person()能打印出年龄为0,说明age有默认初始化值。

然后:构造代码块可以使用非静态,也可以使用静态。

 

设计模式:解决某一类问题最行之有效的方法

         Java中有23种设计模式

   单例设计模式:解决一个类在内存中只存在一个对象

  要想保证方法唯一:1.为了避免其他程序过多建立该对象,先禁止其他程序建立该类对象

                    2.还为了让其他程序能够访问该对象,就要在本类中先建立一个对象

                 3.为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式

       用代码体现:1.将构造函数私有化

                  2.在类中创建一个对象

                  3.提供一个方法可以获取到该对象

对象先初始化:饿汉式。类一进内存就创建了对象。开发常用饿汉式。

对象后初始化:懒汉式。只有调用了方法才建立对象

//饿汉式
public class Single {
	private int num;
	public void setNum(int num){
		this.num=num;
}
	public int getNum(){
		return num;
}
   private Single(){}//把构造函数私有化
   private static Single s=new Single();//获取方法静态,所以该变量静态
   public static Single getInstance(){//要静态
		return s;} 
} 
/*对于事物该怎么描述还是怎么描述。当需要将该事物的对象保证在内存唯一时,就加上这三行代码*/
public class Text {
	public static void main(String[] args){
		Single s=Single.getInstance();
		Single s1=Single.getInstance();
		s.setNum(2);
		System.out.print(s1.getNum());//结果是2
	}
}

在内存中,首先执行主函数时,栈内存开辟了一个空间存储s。然后加载Single类。方法区中存放了Single类中的静态成员。先是有个静态构造函数,然后创建了一个静态变量s,此时,在堆内存中开辟了一个空间,里面创建了一个Single对象,该对象地址值赋给s,s指向该对象。主函数中调用到getInstance()方法时,方法区中s的值赋给栈内存中的s,栈内存中的s也指像了这个对象。因此,主函数中无论定义多少个Single类型的变量,堆内存中的对象只有一个。由最后的打印结果来看,堆中确实只有一个对象,被s和s1以及方法区中的s指向。

如图:

当执行s.setNum(2);时,调用setNum(int sum)函数,栈内存开辟了一个空间,存储该函数中的num和this,2传给参数sum.栈中s的值传给this,this就指向了堆内存中的对象。参数sum的值再赋给对象中的sum.

 如图:



//懒汉式
class Single
{
	private static Single s = null;
	private Single(){}
	public static Single getInstance()
	{
		if(s==null)
		{
			synchronized(Single.class)
			{				
				if(s==null)
					s = new Single();
			}
		}
		return s;
	}
}

//记录原则:定义单例,建议使用饿汉式。

七、继承 extends

将事物的共性描述提取出来,单独进行描述。只要让这个类与单独描述的类有关系即可。

class Person{}

class Student extends Person{}

这就是Student继承Person,student是子类,Person叫做父类或者基类

继承的好处:

         1.提高了代码的复用性。

         2.继承使类与类之间产生关系,有了这个关系才有了多态的特性。

 注意:千万不要为了获取其他类的功能简化代码而继承。必须是类与类之间有所属关系才

可以继承。所属关系:is a。

 

在Java语言中,Java只支持单继承,不支持多继承。因为多继承容易带来安全隐患,比如当多个父类中定义了相同功能但功能内容不同时,子类对象不确定要执行哪一个。(比如A类和B类都有一个叫做work的方法,A中方法体是打印1,B中方法体是打印二,当C类继承A和B时,C类创建的对象调用work方法时,就不知道要打印什么了)但是Java保留了这种机制,并用另一种体现形式来完成,叫做多实现。

Java可以支持多重继承,比如A继承B,B继承C,A类创建的对象就同时具有A、B、C的功能。这就是一个继承体系。

 

如何使用一个继承体系中的功能呢?

想要使用体系,现查阅体系父类的描述,因为父类中定义的是该体系中共性功能。通过了解共性功能就可以知道该体系的基本功能,那么这个体系已经基本可以使用了。

那么在具体调用时,要创建最子类对象,为什么?一是因为有可能父类不能创建对象。二是,创建子类对象可以使用更多功能,包括基本的和特有的。

简单一句话:查阅父类功能,创建子类对象使用功能。

 

事物与事物之间的其他关系:聚集(has a)

1.聚合:比如球队和球员,球队少了一个球员没关系

2.组合:比如心脏和人体,人体少了心脏不行。

 

子父类出现后,类中成员的特点(变量、函数、构造函数):

(1)变量

     Super超级:父类对象的引用。

如果子、父类中出现非私有的同名成员变量时,子类要访问本类中的变量就用this,要访问父类中的同名变量用super。Super和this的使用方法几乎一样,this代表本类对象的引用,super代表父类对象的引用。

(2)函数

     当子类出现与父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容,如同父类的函数被覆盖一样。

     这种情况就是函数的另一种特性:重写(覆盖)。

     当子类继承父类,沿袭了父类的功能到子类中,但子类虽具备该功能,可是功能的内容却和父类不一致,这时,没必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

  比如,之前定义了一个手机类,能够显示号码,现在要它还能显示姓名。那就可以让新手机继承旧手机,然后重写显示方法。代码如下:

//代码一:
class Tel{
	void show(){
		System.out.println("num");
	}
}
class NewTel extends Tel{
	void show(){
	//System.out.println("num");//跟父类重复,可以使用super调用
		super.show();
		System.out.println("name");
		}
}

//代码二:
class Text {
	public static void main(String[] args){
	NewTel t=new NewTel();
	System.out.print(t.num+","+t.num1);//结果是5,4
	System.out.print(t.num+","+t.num);//结果是5,5
	t.show();//结果是3
	}}
class Tel{
	int num1=4;
	int num=3;
}
class NewTel extends Tel{
	int num=5;//把父类的num重写了
	void show(){
	System.out.println(super.num);//若父类中num私有,那就不能访问了
		}//若子类中没有num,那么super可以省略,也可以用this
}

注意:重写(覆盖):1.子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,不然就会编译失败。(默认权限介于public和private之间)

                 2.静态只能覆盖静态(当父类是private时,不能叫重写)

     重载:只看同名函数的参数列表

     重写:子父类方法要一模一样,包括返回值类型。

 比如:
class Tel{
	int show(){//这句跟2不能同时出现在一个函数且不能同时出现在子父类
		System.out.println(2);
		return 1;
			}	
}
class NewTel extends Tel{
	void show(){//2
	System.out.println(1);
		}
}//上述代码编译失败

(3)、子父类中的构造函数

     子类中的构造函数有一句默认的隐式空参数构造函数super();

     在对子类对象进行初始化时,父类的构造函数也运行,那是因为子类的构造函数第一行有一条隐式的super();Super()会访问访问父类中空参数的构造函数,而且子类中所有的构造函数第一行都是super();

 为什么子类一定要访问父类中的构造函数

(如果父类中没有空参数的构造函数,那就必须手动加一个访问父类构造函数)因为父类中的数据,子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何将这些数据进行初始化的。所以子类在对象初始化时要先访问一下父类中的构造函数。如果要访问父类中指定的构造函数,可通过手动定义super语句来实现。

class Tel{
	Tel(int a){
		System.out.println(a);
			}	
}
class NewTel extends Tel{
	NewTel(){//super(2);
	System.out.println(1);
		}
}/*编译失败,因为Tel中自定义了带参数的构造函数,所以隐式空参数的构造函数就没有了。此时,子类中不管有没有显示构造函数,都会编译失败,因为在子类的构造函数第一行有隐式的super();此时在父类中找不到相应的函数。我们可以手动加上super语句。*/

继承中的初始化顺序:

public class Demo {
	public static void main(String[] args) {
	Test1 s1=new Test2();
	}
}
class Test1{
	{
		System.out.println("父类构造代码快");
	}
	static
	{
		System.out.println("父类静态代码快");
	}
	Test1(){
		System.out.println("父类空参构造函数");
	}
}
class Test2 extends Test1{
	{
		System.out.println("子类构造代码快");
	}
	static
	{
		System.out.println("子类静态代码快");
	}
	Test2(){
		System.out.println("子类构造函数");
	}
}
//从结果得出初始化顺序:父类静态代码快--子类静态代码快--父类构造代码快--父类空参构造函数--子类构造代码快--子类构造函数

由于super(参数);语句也要写在子类构造函数第一行,所以子类构造函数要么有this语句要么有super语句,二者不能共存。

结论:

子类的所有的构造函数,默认都会访问父类中空参数的构造函数。

因为子类每一个构造函数内的第一行都有一句隐式super();

 当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问父类中的构造函数。

 当然:子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。

子类中至少会有一个构造函数会访问父类中的构造函数。

 

八、final 最终

1,可以修饰类,函数,变量。

2,被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。

3,被final修饰的方法不可以被复写。

4,被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,有可以修饰局部变量。当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成。单词间通过_连接。

5,内部类定义在类中的局部位置上是,只能访问该局部被final修饰的局部变量。

 

九、抽象

当多个类中出现相同功能,但是功能主体不同,

这是可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。

 

抽象类的特点:

1,抽象方法一定在抽象类中。

2,抽象方法和抽象类都必须被abstract关键字修饰。

3,抽象类不可以用new创建对象。因为调用抽象方法没意义。

4,抽象类中的抽象方法要被使用,必须由子类复写起所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

 

抽象类比一般类多个了抽象函数。就是在类中可以定义抽象方法。

抽象类不可以实例化。

 

特殊:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。

/*模版方法设计模式:在定义功能时,功能的一部分是确定的,但是有一部分是不确定,而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去。由该类的子类去完成。
比如计算一段代码的运行时间:*/
abstract class GetTime{
	public final void getTime()
	{
		long start = System.currentTimeMillis();
runcode();
long end = System.currentTimeMillis();
System.out.println("毫秒:"+(end-start));
	}
	public abstract void runcode();
}
//只要实现该接口,重写runcode方法就可

十、接口 interface

初期理解,可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。

class用于定义类

interface 用于定义接口。

接口定义时,格式特点:

1,接口中常见定义:常量,抽象方法。

2,接口中的成员都有固定修饰符。(这些修饰符可以省略。抽象类的修饰符是自定义的)

         常量:publicstatic final

         方法:publicabstract

记住:接口中的成员都是public的。

接口是不可以创建对象的,因为有抽象方法。需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化。否则子类是一个抽象类。

接口可以被类多实现,即一个类可以同时实现多个接口,也是对多继承不支持的转换形式。java支持多实现。

接口与接口之间是继承关系,接口间存在多继承。

                                                               

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

1,多态的体现

         父类的引用指向了自己的子类对象。

         父类的引用也可以接收自己的子类对象。

2,多态的前提

         必须是类与类之间有关系。要么继承,要么实现。

         通常还有一个前提:存在覆盖。

3,多态的好处

         多态的出现大大的提高程序的扩展性。

4,多态的弊端:

         提高了扩展性,但是只能使用父类的引用访问父类中的成员。(子类后出现,所以不能用子类的特有成员)

判断对象的类型:a instanceof Cat 看对象A是不是Cat类型

class Fu{
	void method1(){};
}
class Zi extends Fu{
	void method2(){};
	public static void main(String[] args){
		Fu f=new Zi();
		f.method1();
		//f.method2();编译失败,编译时,Zi对象未产生,
		//编译器会查看父类是否有着两个方法,没有就编译失败
		//要想使用子类的特有方法,就将f向下转型
		Zi z=(Zi)f;
		z.method2();
	}
}

在多态中非静态成员函数的特点:

在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有编译失败。

在运行时期:参阅对象所属的类中是否有调用的方法。

简单总结就是:非静态成员函数在多态调用时,编译看左边,运行看右边。

在多态中,成员变量的特点:

无论编译和运行,都参考左边(引用型变量所属的类)。

在多态中,静态成员函数的特点:

无论编译和运行,都参考做左边。

 

十二、Object类              

     是所有对象的直接后者间接父类,该类中定义的肯定是所有对象都具备的功能。

      equals(Object obj):Object类中,此方法比较的是两个对象是否为同一个对象,即在堆内存中开辟的空间是不是同一块

      ==:就是比较俩引用的值,即指向内存地址值。所以二者功能一样

十三、内部类

       内部类的访问规则:

1,内部类可以直接访问外部类中的成员,包括私有。之所以可以直接访问外部类中的成员,是因为内部类中持有了一个外部类的引用,格式外部类名.this

2,外部类要访问内部类,必须建立内部类对象。

访问格式:

1,当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中。

可以直接建立内部类对象。

格式

               外部类名.内部类名  变量名 = 外部类对象.内部类对象;

               Outer.Inner in = new Outer().new Inner();

 

2,当内部类在成员位置上,就可以被成员修饰符所修饰。

             比如,private:将内部类在外部类中进行封装。

                   static:内部类就具备static的特性。

                   当内部类被static修饰后,只能直接访问外部类中的static成员。出现了访问局限。

在外部其他类中,直接访问static内部类的非静态成员:

                   newOuter.Inner().function();

在外部其他类中,直接访问static内部类的静态成员:

                   uter.Inner.function();

注意:当内部类中定义了静态成员,该内部类必须是static的。

                     当外部类中的静态方法访问内部类时,内部类也必须是static的。

     3,内部类定义在局部时,

不可以被成员修饰符修饰

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

 

 

匿名内部类:

1,匿名内部类其实就是内部类的简写格式。

2,定义匿名内部类的前提:

         内部类必须是继承一个类或者实现接口。

3,匿名内部类的格式:  new 父类或者接口(){定义子类的内容}

4,其实匿名内部类就是一个匿名子类对象。而且这个对象有点胖。  可以理解为带内容的对象。

5,匿名内部类中定义的方法最好不要超过3个。

interface A{
	void a();
}
class B
{
	public static void main(String[] args){
	new A(){
		public void a()
		{
			System.out.println();
		}}.a();//直接重写a方法并调用
	A z=new A()//调用的方法多于两个 就可以考虑起名字
	{
		public void a()
		{
			System.out.println();
		}
	    void s(){}
	};
	//z.s();编译失败,父类引用不能调用子类特有方法
	z.a();}}

十四、异常

就是程序在运行时出现不正常情况。

异常由来:问题也是现实生活中一个具体的事物,也可以通过java的类的形式进行描述。并封装成对象。其实就是java对不正常情况进行描述后的对象体现。

对于问题的划分:两种:一种是严重的问题,一种非严重的问题。

对于严重的,java通过Error类进行描述。对于Error一般不编写针对性的代码对其进行处理。

对与非严重的,java通过Exception类进行描述。对于Exception可以使用针对性的处理方式进行处理。

无论Error或者Exception都具有一些共性内容。比如:不正常情况的信息,引发原因等。

Throwable                        

         |--Error

         |--Exception

JVM有异常处理机制,没有手动处理异常时,就会自动处理,处理的方式是停止运行。

对多异常的处理。

1,声明异常时,建议声明更为具体的异常。这样处理的可以更具体。

2,对方声明几个异常,就对应有几个catch块。不要定义多余的catch块。

         如果多个catch块中的异常出现继承关系,父类异常catch块放在最下面。

建立在进行catch处理时,catch中一定要定义具体处理方式。

不要简单定义一句 e.printStackTrace(),

也不要简单的就书写一条输出语句。

 

自定义异常

因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象。所以对于这些特有的问题可以按照java的对问题封装的思想。将特有的问题。进行自定义的异常封装。必须是自定义类继承Exception。

继承Exception原因:

异常体系有一个特点:因为异常类和异常对象都被抛出。他们都具备可抛性。这个可抛性是Throwable这个体系中独有特点。只有这个体系中的类和对象才可以被throws和throw操作。

 

RuntimeException:

Exceptoin中有一个特殊的子类异常RuntimeException运行时异常。如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。如果在函数上声明了该异常。调用者可以不用进行处理。编译一样通过;之所以不用在函数声明,是因为不需要让调用者处理。当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,

对代码进行修正。

自定义异常时:如果该异常的发生,无法在继续进行运算,就让自定义异常继承RuntimeException。

异常在子父类覆盖中的体现;

1,子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类。

2,如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。

3,如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。

         如果子类方法发生了异常。就必须要进行try处理。绝对不能抛。

 

throw和throws的用法:

throw定义在函数内,用于抛出异常对象。

throws定义在函数上,用于抛出异常类,可以抛出多个用逗号隔开。

当函数内容有throw抛出异常对象,并未进行try处理。必须要在函数上声明,都在编译失败。注意,RuntimeException除外。也就说,函数内如果抛出的RuntimeExcpetion异常,函数上可以不用声明。

如果函数声明了异常,调用者需要进行处理。处理方法可以throws可以try。

 关于异常的几个常见方法:

        String getMessage()//获取异常信息。
        String toString() //异常名称 : 异常信息。
        printStackTrace()//异常名称,异常信息,异常出现的位置。其实jvm默认的异常处理机制,就是在调用printStackTrace方法。打印异常的堆栈的跟踪信息。

异常有两种:

         编译时被检测异常。该异常在编译时,如果没有处理(没有抛也没有try),编译失败。该异常被标识,代表这可以被处理。

         运行时异常(编译时不检测)。在编译时,不需要处理,编译器不检查。该异常的发生,建议不处理,让程序停止。需要对代码进行修正。

          所以,在自定义异常时就要搞清楚该异常种类从而判断是继承Exception还是继承RuntimeException

 

异常的处理原则:

1,处理方式有两种:try 或者 throws。

2,调用到抛出异常的功能时,抛出几个,就处理几个。一个try对应多个catch。

3,多个catch,父类的catch放到最下面。

4,catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,输出语句。也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。

   

 try
                   {
                            thrownew AException();
                   }
                   catch(AException e)
                   {
                            throwe;
                   }

如果该异常处理不了,但并不属于该功能出现的异常。可以将异常转换后,在抛出和该功能相关的异常。或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,当调用者知道。并处理。也可以将捕获异常处理后,转换新的异常。

                  

 try
                   {
                            thrownew AException();
                   }
                   catch(AException e)
                   {
                            // 对AException处理。
                            thrownew BException();
                   }

异常的注意事项:

         在子父类覆盖时:

         1,子类抛出的异常必须是父类的异常的子类或者子集。

         2,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。

注意:

1,finally中定义的通常是关闭资源代码。因为资源必须释放。

2,finally只有一种情况不会执行。当执行到System.exit(0);fianlly不会执行。


//毕老师用电脑上课的例子:

class Teacher{
	private String name;
	private Computer c;
	Teacher(String name){
		this.name=name;
		c=new Computer();
	}
	void shangke()throws KeException{
		try{
		c.run();
		System.out.println("开始上课");
		}
		catch(MaoException e){
			throw new KeException("无法上课");
		}
		catch(LanException e)
		{
			c.rer();
			System.out.println("继续上课");
		}}}
class KeException extends Exception{
	KeException(String s){
		super(s);
	}
}
class LanException extends Exception{
	LanException(String s){
		super(s);
	}
}
class MaoException extends Exception{
	MaoException(String d){
		super(d);
	}
}
class Computer {
	int num=3;
	void run()throws LanException,MaoException {
		if(num==2)
			throw new LanException("电脑蓝屏了");
		if(num==3)
			throw new MaoException("电脑冒烟了");
		System.out.println("电脑运行");
	}
	void rer(){
		num=1;
		System.out.println("电脑重新运行");
	}
}
public class Exc {
public static void main(String[] args) {
		try{
		new Teacher("毕老师").shangke();
		}
		catch(KeException d){
			System.out.println("放假");
		}}}

包 package

  1、对类文件进行分类管理

  2、给类提供多层次名空间

  3、写在程序文件的第一行

  4、类名的全称是 包名.类名

  5、包也是一种封装形式

  6、包的出现可以使.class文件和源文件相分离

包与包之间进行访问,被访问的包中的类以及类中的成员,需要public修饰。

不同包中的子类还可以直接访问父类中被protected权限修饰的成员。(非子类不能访问)

包与包之间可以使用的权限只有两种,public  protected。

在命令行中的编译命令:javac –d 目录 类名.Java

运行时:Java 包名.类名

 一个Java文件不能存在两个以上被public修饰的类或者接口,因为被public修饰后文件名要与类名一致,可以重新建个Java文件。

包中还可以有包,package A.B.C :A中B,B中有C。

为了简化类名的书写,使用一个关键字,import.import 导入的是包中的类。

建议,不要写通配符 * ,需要用到包中的哪个类,就导入哪个类。

 

Jar包

Java的压缩包

命令:jar –cf a.jarpack1 pack2

      将pack1和pack2打包到a.jar

查看:jar –tf a.jar

将jar文件目录设置到classpath中,就可以直接使用jar包的内容


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值