JavaSE实战——面向对象(上) 封装,继承,对象初始化流程,单例设计模式

转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/44830733

面向对象概述

    面向对象语言:C++、C#、Java。这其中的Java,是一门严谨性的语言。

    面向对象有三大特征:封装、继承、多态。

    面向对象特点:
    1. 将复杂问题简单化。符合人们思考习惯的一种思想。
    2. 从过程的执行者,转换成了对象的指挥者。
    3. 基于面向过程,将过程进行对象的封装。解决问题直接找对象就可以了。

    万物皆对象。例如:调用行内人去电脑城帮买配件砍价、面试官调用面试者编程。水杯装水倒水功能、手机打电话发信息功能。

    过程和对象在我们的程序中是如何体现的呢?

    过程其实就是函数。对象是将函数等一些内容进行了封装。
    类:用于描述事物的。对象:该类事物具体的个体。

    new一个新的对象,参考的是该类的.class文件,不是源码。
    汽车:

       属性:轮胎数,颜色。
       行为:运行。

成员变量和成员函数    

    在类中定义其实都称之为成员。成员有两种:
      1:成员变量:其实对应的就是事物的属性。
    2:成员函数:其实对应的就是事物的行为。
    所以,其实定义类,就是在定义成员变量和成员函数。但是在定义前,必须先要对事物进行属性和行为的分析,才可以用代码来体现。
    成员变量和局部变量的区别:
1. 源代码中定义的位置不同:
      成员变量定义在类中。
      局部变量定义在方法中,也可以定义在语句中。
      (只要是类的下一级大括号都是局部的。局部变量:方法中、参数上、语句中)
2. 作用域不同:
      成员变量在这个类中有效。
      局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。
3. 内存中的存储位置不同:
     成员变量存储在堆内存的对象中。
     局部变量存储在栈内存的方法中。
4. 生命周期不同:
     成员变量随着对象的出现而出现。
     局部变量随着所属区间的运行出现,随着所属区间的结束而释放。
    (局部变量随着方法的加载而出现,随着方法的消失而消失。)

class Car{
	int num;
	String color;
	void run(){
		System.out.println(num+"|"+color);
	}
}
/*要想使用Car类中的内容,必须先创建Car的对象*/
class CarDemo{
	public static void main(String[] args){
		Car c1 = new Car();
		Car c2 = new Car();
		
		c1.run();
		
		show(c1);
		show(c2);
		
		show(new Car());//匿名对象可以作为实际参数传递。
		/*
			如果对象对方法只进行一次调用,可以简化为匿名对象的形式
			new Car().run();
			用完即被垃圾回收机制处理。
		*/
	}
	public static void show(Car c){//Car c = c1;//类类型参数,接收的是对象(所在地址)。
		System.out.println(c);//Car@1db9742
		c.num = 4;
		c.color = "red";
		c.run();
	}
}
封装

    封装:隐藏实现细节,对外提供公用的访问方式(接口)。
    封装的体现之一:将属性(成员变量)都私有化(private),对外提供对应的setXXX、getXXX方法来访问。(一些成员方法也可以私有化)
    封装的好处:
    1. 提高安全性。不允许直接访问细节,并通过公共的方式访问,可以实现可控。
    2. 提高易用性。不需要知道细节,使用起来比较简单。
    3. 提高复用性。
    4. 隔离了细节实现的变化。
    权限修饰符:private
    对私有的数据,可以通过方法的方式对其进行访问。
    注意:私有仅仅是封装的一种体现形式而已。
    属性前加了private修饰,只是访问权限降低,只可在本类被访问,但是创建对象后,相应的堆内存中,该属性还是真实存在的。
    私有的成员:
其他类不能直接创建对象访问,所以只有通过本类对外提供具体的访问方式(函数)来完成对私有的访问,

    好处:可以在函数中加入逻辑判断等操作,对数据进行判断等操作。
    总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,
    所以,类中的属性通常被私有化,并对外提供公共的访问方法。
    这个方法一般有两个,规范写法:对于属性 xxx,可以使用setXXX(),getXXX()对其进行操作。

class Person{
	private int age;//私有的访问权限最低,只有在本类中的访问有效
	public void setAge(int a){
		/*可以对数据进行控制。所以应该对属性私有化,对外提供set、get方法的原因。*/
		if (a < 0 || a > 130){
			//System.out.println(a+", 数值是错误的");
			/*抛出异常*/
			throw new RuntimeException(a+", 数值是错误的");
		}else{
			age = a;
		}
	}
	public int getAge(){
		return age;
	}
	void speak(){
		System.out.println("age = "+age);
	}
}
class PersonDemo{
	public static void main(String[] args){
		Person p = new Person();
		//p.age = 23;
		p.setAge(-20);
		p.speak();
	}
}
    类中怎么没有定义主函数呢?
    注意:主函数的存在,仅为该类是否需要独立运行,如果不需要,主函数是不用定义的。
    主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。

    main函数解析:
    public static void main(String[] args)
    主函数的特殊之处:
    1. 格式是固定的。
    2. 被jvm所识别和调用。
    public:因为权限必须是最大的。
    static:不需要对象的,直接用主函数所属类名调用即可。
    void:主函数没有具体的返回值。
    main:函数名,不是关键字,只是一个jvm识别的固定的名字。
    String[] args:这是主函数的参数列表,是一个数组类型的参数,而且元素都是字符串类型。

    jvm调用main方法时,传递的实际参数是 new String[0]。jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。

/*基本数据类型参数传递(传值)(栈)*/
class Demo{
	public static void main(String[] args){
		int x = 4;
		show(x);
		System.out.println("x = "+x);
	}
	public static void show(int x){
		x = 5;
	}
}

/*引用数据类型参数传递(传址)(栈、堆)*/
class Demo{
	int x = 6;/*显示初始化。*/
	public static void main(String[] args){
		Demo d = new Demo();
		d.x = 8;
		//show(d);
		show(new Demo());
		System.out.println("x = "+d.x);
	}
	public static void show(Demo d){
		d.x = 7;
	}
}
构造函数

    主函数:固定格式,给虚拟机用,应用程序运行入口。
    一般函数:用于
描述事物应该具备的功能。
    构造函数:也是功能,比较特殊,专门用于给对象进行初始化。

class Person{
	private String name;
	private int age;
	/*定义一个构造函数,用于给Person对象初始化*/
	//Person(){} //类中默认的空参构造函数。专门用于创建对象初始化用的。
	Person(){
		name = "baby";
		return;//return是结束函数用的。不写会默认加上。
	}
	Person(String n){
		name = n;
	}
	Person(String n, int a){
		if (a<0)
			return;//提前结束初始化。
		this.name = n;
		this.age = a;
	}
	public void setName(String n){
		name = n;
	}
	public String getName(){
		return name;
	}
	public void setAge(int a){
		age = a;
	}
	public int getAge(){
		return age;
	}
	
	public void show(){
		System.out.println("name = "+name+", age = "+age);
	}
}
class PersonDemo{
	public static void main(String[] args){
		Person p = new Person("wangcai",22);
		/*
			创建对象时,先默认初始化,该对象的所有成员变量都取默认值(null,0).
			然后调用相应的构造函数初始化变量。
		*/
		//p.setName("xiaoqiang");//虽然有了构造函数,但是setXXX方法还是必要的。
		//p.setAge(23);
		p.show();
		
		Person p1 = new Person();
		p1.show();
	}
}
     构造函数格式:
    1. 函数名和类名相同。
    2. 没有返回值类型。
    3. 没有具体的返回值。
    4. 有return语句,用于结束初始化的。
    5. 构造函数可以私有化,只在本类中使用,本类中的其他构造函数可以通过this(xxx)访问本类中已经私有化的构造函数。
(而且一旦所有构造函数私有化,其他程序就无法创建该类对象,因为无法对创建的对象进行初始化)
    一般函数和构造函数的区别:
    1.构造函数在对象创建时就执行了,用于初始化,而且初始化动作只执行一次。

      一般函数是在对象创建后,需要时才被对象调用。可以调用多次。
    2.二者定义格式不同,
      一般函数命名时,第一个单词首字母小写;
      构造函数命名每个单词首字母大写,和类名相同。
   
多构造函数在类中的体现就是重载形式。

   注意一:定义的每一个类中,都有一个默认的空参数构造函数。

   注意二:一旦在类中自定义了构造函数后,默认的构造函数就没有了。

this关键字

    构造函数私有,只在本类中有效,该如何访问呢?
    注意:构造函数只能被构造函数调用(this)。不能直接被一般方法调用。但构造函数可以调用一般方法。一般方法可以调用一般方法(this)。(因为一般方法被对象调用,对象需要先通过构造函数初始化才会存在)。

    构造函数之间该如何访问呢?使用this关键字。

class Person{
	private String name;
	private int age;
	
	Person(){
		name = "baby";
	}
	private Person(String n){
		this.name = n;
	}
	Person(String n, int a){
		this(n);//构造函数可以调用构造函数,也可以调用一般函数
		this.age = a;
	}
	
	public void setName(String n){
		name = n;
	}
	public String getName(){
		return name;
	}
	public void setAge(int a){
		age = a;
	}
	public int getAge(){
		return age;
	}
	
	public void show(){
		System.out.println("name = "+this.name+", age = "+age);
	}
}
class ThisDemo{
	public static void main(String[] args){
		Person p = new Person("xiaoming",24);
		p.show();
	}
}
     this:代表的是对象。哪个对象调用this所在的函数,this就代表哪个对象,就是哪个对象的引用。
    开发时,什么时候使用this呢?
    在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
    this 还可以用于构造函数间的调用。
    this带上参数列表的方式,就可以访问本类中的其他构造函数。
    例如:this("xiaoqiang"):访问的就是本类中,带一个字符串参数的构造函数。
    调用格式:this(实际参数);
    this对象后面跟上. :调用的是成员属性和成员方法(一般方法);
    this对象后面跟上():调用的是本类中的对应参数的构造函数。
    注意:用this调用构造函数,必须定义在构造函数的第一行!(因为构造函数是用于初始化的,所以初始化动作一定要先执行。 否则编译失败 )。一个构造函数里不可以使用两次this,即一个构造函数中不可以调用两个构造函数。

private Person(String n){
		this("lisi",40);
	}
	Person(String n,int a){
		this(n);
		age = a;
	}
	Person p = new Person("xiaoming",23);
    这样递归会造成栈溢出。
    总结:凡是访问了对象中的数据的方法都持有this引用。或者说,凡是直接被对象调用的方法都持有this引用。

    练习:当成员变量和局部变量同名时,可以通过this关键字区分,代码如下所示:

class Person{
	private String name;
	private int age;
	Person(){
		this.name = "baby";
	}
	private Person(String name){
		this.name = name;
	}
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	
	public void setName(String n){
		name = n;
	}
	public String getName(){
		return name;
	}
	public void setAge(int a){
		age = a;
	}
	public int getAge(){
		return age;
	}
	
	public void show(){
		System.out.println("name = "+this.name+", age = "+this.age);
	}
	public void method(){
		this.show();//一般函数可以调用一般函数,但是不可以调用构造函数
	}
	public boolean sameAge(Person p){
		return this.age == p.age;
		//return this == p;//返回的是对象的地址是否相同,是否是同一个人。
	}
}
class ThisDemo2{
	public static void main(String[] args){
		Person p1 = new Person("xiaoqiang",23);
		p1.method();
		Person p2 = new Person("wangcai",22);
		p2.method();
		boolean r = p1.sameAge(p2);
		System.out.println(p1.getName()+" and "+p2.getName()+" are same age? "+r);
	}
}
static成员修饰符

    什么时候函数需要静态修饰呢?该函数没有访问过对象中的属性时,就需要静态修饰。例如:

class Person{
	private String name;
	private int age;
	static String country = "CN";
	//public static String country = "CN";//全局静态变量,直接类名调用即可使用。
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	public void show(){
		System.out.println("name = "+this.name+", age = "+this.age+", country = "+Person.country);
	}
	public static void sleep(){
		System.out.println("呼呼..."+Person.country);
	}
}
class StaticDemo{
	public static void main(String[] args){
		Person.sleep();
		System.out.println(Person.country);
		Person p = new Person("xiaoli",22);
		p.show();
		System.out.println(args.length);
		for (int x = 0; x < args.length; x++)
			System.out.println(args[x]);
	}
}
    特点:
     1. 被静态修饰的成员,可以直接被类名所调用。
     2. 静态成员优先于对象存在。(共享的先存在)
     3. 静态成员随着类的加载而加载,随着类的消失而消失。静态成员生命周期很长。
   注意事项:
    1. 静态方法只能直接访问静态成员,不能访问非静态成员。(这就是静态方法的访问局限性) (因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。非静态成员需要对象来调用(非静态成员都有this所属))
    2. 非静态方法可以访问静态成员。
    3. 静态的方法中不能出现this或者super关键字。(因为this代表对象,而静态在时,有可能没有对象,所以this无法使用)
    4. 主函数是静态的。
   备注:
    1. 解释main静态: 静态只能直接通过类名调用静态,通过new方法创建一个对象后,也能通过对象间接调用非静态。
    2. static的变量或方法都是类加载后就可以用的,不必调用构造函数生成对象。
   用法:
    成员变量:如果数据在所有对象中都是一样的,直接静态修饰。
    成员函数:如果函数没有访问过对象中的属性数据,那么该函数就是静态的。
    成员变量和静态变量的区别:
    1. 名称上的区别:
           成员变量也叫实例变量。
           静态变量也叫类变量。
    2. 内存存储上的区别:
           成员变量存储到堆内存的对象中。
           静态变量存储到方法区的静态区中。
    3. 生命周期不同:
           成员变量随着对象的创建而存在,随着对象的回收而消失。(多对象->多份)
           静态变量随着类的加载而存在,随着类的消失而消失。(多对象->一份)
   4. 调用方法不同:
           成员变量只能被对象所调用。
           静态变量可以被对象调用,也可以被类名调用。
   所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

静态代码块

    静态代码块:随着类的加载而执行,而且只执行一次。(new多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。

    作用:给类进行初始化的。
    应用场景:类不需要创建对象。但是需要初始化,这时将部分代码存储到静态代码块中。

class StaticCode{
	static{//静态代码块,只能访问静态成员
		System.out.println("A");
	}
	static void show(){
		System.out.println("show run");
	}
}
class StaticCodeDemo{
	static{
		System.out.println("B");
	}
	public static void main(String[] args){
		StaticCode.show();
		StaticCode.show();
	}
	static{
		System.out.println("C");
	}
}

    例如Java加载数据库驱动的时候,只需要使用Class.forname(DBDRIVER)就可以了,原理就是强制要求JVM查找并加载指定的数据库驱动类(例如mysql-connector-java-5.1.24-bin.jar.org.gjt.mm.mysql.Driver)进内存,因为该类中有静态初始化器,JVM必然会执行该类的静态代码段,而在JDBC规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBC Driver的Driver类的代码都必须类似如下:

public class MyJDBCDriver implements Driver{
	static{
		DriverManager.registerDriver(new MyJDBCDriver());
	}
}

    既然在静态初始化器中已经进行了注册,所以我们在使用JDBC时只需要Class.forname(XXX.XXX)就可以了。

构造代码块

    构造代码块。给所有对象进行初始化,只要对象一建立,就会调用这个代码块。

    构造函数只给对应的对象进行初始化,它具有针对性。下面的代码中有构造代码块。

对象的初始化流程

     静态代码块(首次加载类)->默认初始化->super()访问父类构造函数->显示初始化->构造代码块->构造函数->引用首地址值

class TestPerson{
	private int age = 7;
	{
		age = 6;
		System.out.println("TestPerson constructor code run..."+age);
	}
	TestPerson(){
		this.age = 5;
		System.out.println("TestPerson() run..."+age);
	}
}
class Person extends TestPerson{
	private int age = 8;//显示初始化
	private static int test = 1;//这不是显示初始化。
	
	{//构造代码块。给所有对象进行初始化,只要对象一建立,就会调用这个代码块。构造函数只给对应的对象进行初始化,它具有针对性。
		System.out.println("constructor code run..."+age);
		cry();//将重载的构造函数的共性部分,放在构造代码块中。
	}
	static{//静态代码块,只能访问静态成员。随着类的加载而执行,仅一次。
		test = 10;
		System.out.println("static code run..."+test);
	}
	Person(){
		//cry();
		/* 构造函数中,先执行隐式部分:
		1,super();//调用父类构造函数
		2,显示初始化。
		3,构造代码块初始化。
		*/
		System.out.println("Person() run..."+age);//构造函数自定义的初始化代码
	}
	Person(int age){
		//cry();
		this.age = age;
		System.out.println("Person(age) run..."+age);
	}
	public void cry(){
		System.out.println("哇哇哇!");
	}
}
class ConsCodeDemo{
	public static void main(String[] args){
		Person p1 = new Person();
		
		Person p2 = new Person(23);
		int x = 3;
		{//局部代码块。可以控制局部变量的生命周期。
			//int x = 3;
			System.out.println("哈哈!");
		}
		System.out.println("x = "+x);
	}
}

    创建一个对象的流程://编译时只检查代码语法正确性,运行时才会在内存中创建对象
   1. 通过java ConsCodeDemo命令行,启动虚拟机,加载一系列东西(比如Object.class),
       接着加载硬盘上指定位置(classpath)的ConsCodeDemo.class字节码文件进内存方法区,
       并会先加载ConsCodeDemo的父类(如果有直接的父类的情况下)。
   2. 如果是第一次加载这个类,则会执行静态代码块,给类进行初始化。

       如果之前已经加载过这个类,不再执行静态代码块。
   3. jvm虚拟机识别,并执行main方法,在栈内存中开辟了main方法的空间(压栈-进栈)。(主方法进栈)
   4. 对上述main方法中的第一行语句,先执行等号右边,在执行等号左边;
       先查找classpath路径,再查找当前路径,直到找到Person.class字节码文件,

       先加载Person的父类(如果有),再加载Person进内存方法区。
   5. 如果是第一次加载这个类,则会执行静态代码块,给类进行初始化。
   6. 通过new在堆内存中开辟实体空间,分配首地址值。(接着进行一系列初始化过程)
   7. 在该实体空间中进行属性的空间分配,对对象中的属性进行默认初始化。(非静态的成员变量称为属性)
   8. 调用与对象对应的构造函数。构造函数压栈。
   9. 构造函数中先执行第一行隐式的语句super()访问父类中相应的构造函数。

       (不写,默认第一行是super();写了,就是super(实参列表))
   10.父类初始化完毕后,对对象的非静态属性进行显示初始化。(静态属性已经随着类的加载、静态代码块的执行,初始化完毕)
   11.调用类中的构造代码块初始化。
   12.执行构造函数中自定义的初始化代码。
   13.初始化完毕,在main方法的栈区分配(创建)一个引用变量p,将实体的首地址值赋值给p,

        p变量就引用了该实体。(指向了该对象)
       (当然了,这个引用变量,不一定就是在栈里面,也可能在堆、方法区的静态区中,关键看在哪里声明的。)

单例设计模式

    解决的问题:保证一个类的对象在内存中的唯一性。
    应用场景:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。

class Single{ //饿汉式
	// 创建一个本类对象,并且私有化,实现可控。
	private static final Single s = new Single();
	// 私有化构造函数,不让其他程序创建该类对象。
	private Single(){}
	// 对外提供一个可控的方法返回该对象,让其他程序可以获取到。
	public static Single getInstance(){
		return s;
	}
}
/*另一种形式:对象延迟加载方式。*/
class Single{ //懒汉式
	private static Single s = null;
	private Single(){}
	public static Single getInstance(){
		if (s == null)
			s = new Single();
		return s;
	}
}
class SingleDemo{
	public static void main(String[] args){
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();
		System.out.println(s1 == s2);
		
		SuperMan man1 = SuperMan.getInstance();
		SuperMan man2 = SuperMan.getInstance();
		man2.setName("英雄");
		System.out.println("man1.name = "+man1.getName());
		System.out.println("man2.name = "+man2.getName());
	}
}
class SuperMan{
	private String name;
	private static SuperMan man = new SuperMan("克拉克");
	private SuperMan(String name){
		this.name = name;
	}
	public static SuperMan getInstance(){//不是每个人都能呼唤出超人,可以实现可控。
		return man;
	}
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
}
     如何保证对象唯一性呢?
    思想:
  1,不让其他程序创建该类对象。
  2,在本类中创建一个本类对象。
  3,对外提供方法,让其他程序获取这个对象。
    步骤:
  1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;
  2,就在类中创建一个本类的对象;
  3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控)
 代码体现:
  1,私有化构造函数;
  2,创建私有并静态的本类对象;
  3,定义公有并静态的方法,返回该对象。
    练习:
class Test{
	private int num;
	private static Test t = new Test();
	private Test(){}
	public static Test getInstance(){
		return t;
	}
	public void setNum(int num){
		this.num = num;
	}
	public int getNum(){
		return this.num;
	}
}
class TestDemo{
	public static void main(String[] args){
		Test t1 = Test.getInstance();
		Test t2 = Test.getInstance();
		t1.setNum(3);
		t2.setNum(4);
		System.out.println("t1.num = "+t1.getNum());
		System.out.println("t2.num = "+t2.getNum());
	}
}
继承

    Java支持单继承。不直接支持多继承,但是保留了这种多继承机制,进行改良。

    好处:

    1. 提高了代码的复用性。
    2. 让类与类之间产生了关系。为第三个特征多态提供了前提。

class Person{
	String name;//细节:对于父类中私有的部分,子类对象是无法直接访问的。但是也仅仅是访问权限降低而已,创建子类对象后,对应的堆内存中,该属性还是真实存在的,可以通过set,get方法访问即可。
	int age;
}
class Student extends Person{
	void study(){
		System.out.println(this.name+" good good "+this.age);
	}
}
class Worker extends Person{
	void work(){
		System.out.println("hard");
	}
}
class ExtendsDemo{
	public static void main(String[] args){
		Student s = new Student();
		s.name = "小明";
		s.age = 23;
		s.study();
	}
}

     1.单继承:一个类只能有一个父类。
    2.多继承:一个类可以有多个父类。
         优势:可以让子类具备更多功能。
         弊端:调用的不确定性,因为方法的主体不同。java对其进行改良,接口多实现、接口之间多继承方式,后面会讲到。
    3.多重继承:A继承B  B继承C  C继承D。
    多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的是该体系最基本最共性内容的功能。所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。
 简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。 

    什么时候使用继承呢?
    当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。a extends b。狼是犬科中的一种。英文书中,所属关系:" is a "。
    注意:不要仅仅为了获取其他类中的已有成员进行继承。例如:

class Demo1{
		void method1(){}
		void method2(){}
	}
	class Demo2 extends Demo1{//Demo2可以获取到Demo1中的method1,但是不应该具备method2,不存在继承。
		//void method1(){}
		void method3(){}
	}
    但是Demo1、Demo2具备共性,可以抽取。
class Demo{
		void method1(){}
	}
	class Demo1 extends Demo{
		void method2(){}
	}
	class Demo2 extends Demo{
		void method3(){}
	}
	new Demo1().method1();
	new Demo1().method2();
      所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。

    加载顺序:父类比子类先加载进内存方法区。
    调用顺序:查找属性或者方法时,先在当前方法中查找,然后去子类对象或子类空间中查找,最后去父类空间中查找。并且,同名方法始终是子类的覆盖父类的,即使是在父类中调用该方法的语句;同名变量始终是子父类分开,共存于子类对象中,即父类中没有标明,也是操作自己的成员变量。
    下面分三个方面,讲述在子父类中成员的特点体现:
    1.成员变量。
    2.成员函数。
    3.构造函数。

super关键字

class Fu{
	int num = 4;//即使private,子类对象中也有。
}
class Zi extends Fu{
	int num = 5;
	void show(){
		int num = 6;
		System.out.println("num = "+num);
		System.out.println("num = "+this.num);//当本类的成员和局部变量同名用this区分。
		System.out.println("num = "+super.num);//当子父类中的成员变量同名用super区分父类。
		System.out.println("this: "+this);//this: Zi@1db9742,@左边是实体类型,@右边是实体在内存中位置的哈希值。
		//System.out.println("super: "+super);//编译失败!super不是父类对象的引用。
	}
}
class ExtendsDemo2{
	public static void main(String[] args){
		Zi z = new Zi();
		System.out.println("z: "+z);//z: Zi@1db9742
		z.show();
	}
}
      this:代表一个本类对象的引用。

    super:代表一个父类空间。(不是父类对象的引用!可以理解为在方法区中,子类空间中super指向父类空间。)
    注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。

class Fu{
	private int num = 4;
	public int getNum(){
		return num;
	}
}
class Zi extends Fu{
	private int num = 5;
	public void show(){
		System.out.println(this.num+"..."+super.getNum());
	}
}
class ExtendsDemo2{
	public static void main(String[] args){
		new Zi().show();
	}
}
覆盖(重写、复写)

    成员函数:当子父类中出现一模一样的方法时,子类对象运行的是子类的方法。(当然了,这时需要保证,如果子类方法不用的情况下,父类的方法还可以使用)
    这种特殊的情况,称之为覆盖,override

class Fu{
	public static void show(){
		System.out.println("Fu show run");
	}
}
class Zi extends Fu{
	public static void show(){
		System.out.println("Zi show run");
	}
}
class ExtendsDemo3{
	public static void main(String[] args){
		Zi z = new Zi();
		z.show();
		NewPhone p = new NewPhone();
		p.show();
	}
}
//描述手机:打电话,发短信,来电显示。
class Phone{
	public void call(){}
	public void sendMsg(){}
	public void show(){
		System.out.println("number");
	}
}
//手机出新了,来电显示更给力。
class NewPhone extends Phone{
	//定义了来电显示功能。注意了,父类已经定义了来电显示的功能,子类直接拿过来用就可以了。
	//但是如果,子类对功能的内容要有自己的定义。那就保留父类功能的声明,建立子类功能特有的内容:覆盖的应用。
	public void show(){
		//System.out.println("number");
		super.show();//如果show是静态的,不可以使用super。因为静态方法先于对象加载,没有对象存在,用类名亦可调用show,但是super必须有父类对象。
		System.out.println("name");
		System.out.println("pic");
	}
}
     函数的两个特性:
    1.重载:本类中。overload。(参数个数 或者参数类型 或者参数顺序不同)

    2.覆盖:子类中。override。(是否能被外界访问到,是否静态,返回值类型,函数名,参数列表都一致)(又叫复写,重写)
    什么时候使用覆盖操作?
    当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就使用覆盖操作完成。
    覆盖的注意事项:
    1.子类方法覆盖父类方法,必须要保证权限大于等于父类权限。(public > 默认权限 > private)
      (父类private,子类public,不叫覆盖,因为原本的父类方法就无法访问,不符合覆盖定义)
    2.覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
      (并且,如果覆盖操作,子父类的方法都是静态的,则静态方法体中不可以使用this或者super,它俩是基于对象的,而静态成员先于对象存在)

构造函数

    构造函数的开头,都有隐式的三条语句,分别是:

    1, super(); 调用父类构造函数
    2, 显示初始化。
    3, 构造代码块初始化。

class Fu extends Object{
	/*Fu(){
		//super();
		System.out.println("Fu run");
	}*/
	Fu(int x){
		System.out.println("Fu run..."+x);
	}
}
class Zi extends Fu{
	Zi(){
		//super();
		//super(4);//显示指定super的方式来访问父类中的构造函数。
		this(5);//是可以的,有了this语句,就没有super语句。
		System.out.println("Zi run");
	}
	Zi(int x){
		//super();
		super(x);
		System.out.println("Zi run..."+x);
	}
}
class ExtendsDemo4{
	public static void main(String[] args){
		Zi z = new Zi();
		System.out.println("----------------");
		new Zi1(6);
	}
}
class Fu1{
	int num;
	Fu1(){
		num = 10;
		System.out.println("A Fu run");
	}
	Fu1(int x){
		System.out.println("B Fu run"+x);
	}
}
class Zi1 extends Fu1{
	int num;
	Zi1(){
		//super();
		System.out.println("C Zi run"+num);
	}
	Zi1(int x){
		this();
		//super();
		//super(x);
		System.out.println("D Zi run"+x);
	}
}
     1.发现子类构造函数运行时,先运行了父类的构造函数。为什么呢?
        因为子类中所有的构造函数的第一行默认都有一个隐式的super();语句。
        调用本类中的构造函数用this(实参列表)语句。
        调用父类中的构造函数用super(实参列表)语句。
    2.为什么子类对象初始化都要先访问父类中的构造函数呢?
        因为子类继承了父类中的内容,所以创建对象时必须要先看父类是如何对内容进行初始化的。这就是子类的实例化过程。
    注意:
    1.当父类中没有空参数构造函数时,子类需要通过显示定义super语句指定要访问的父类中的构造函数。
    2.用来调用父类构造函数的super语句,在子类构造函数中必须定义在第一行,因为父类的初始化要先完成。
    3.子类所有的构造函数中,至少保证要有一个构造函数要通过super调用父类构造函数。(构造子类对象,一定要先进行父类初始化一次)
    问题:
    1.this和super用于调用构造函数,可以同时存在吗?
       不可以,因为他们只能定义在第一行。
    2.用于调用构造函数的this和super语句,为什么需要定义在第一行?
       因为初始化动作要先执行。

    继承的小练习(掌握代码复用):

    父类Person属性:name,age,一初始化就必须有姓名和年龄。行为:setXXX  getXXX.
    子类有:student,worker,他们初始化时也要有姓名和年龄。

class Person{
	private String name;//父类中的private属性,子类对象中是存在的。
	private int age;
	Person(String name, int age){
		this.name = name;
		this.age = age;
	}
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
	public void setAge(int age){
		this.age = age;
	}
	public int getAge(){
		return this.age;
	}
}
class Student extends Person{
	Student(String name, int age){
		super(name,age);//子类对象调用的相应父类构造函数,父类构造函数进栈也持有子类对象的this引用。
	}
	public void study(){
		System.out.println("student "+super.getName()+" study");
	}
}
class Worker extends Person{
	Worker(String name, int age){
		super(name, age);
	}
	public void work(){
		System.out.println("worker "+super.getName()+" work");
	}
}
class ExtendsDemo5{
	public static void main(String[] args){
		Student stu = new Student("lichunchun",23);
		stu.study();
	}
}
    继承中覆盖的陷阱:
class Fu{
	int num;
	Fu(){
		super();
		num = 20;//这句就是在给父类属性赋值。
		show();//show如果被子类方法覆盖,会执行子类show方法。
		return;
	}
	void show(){
		System.out.println("fu show");
	}
}
class Zi extends Fu{
	int num = 8;
	Zi(){
		super();
		//-->通过super初始化父类内容时,子类的成员变量并未显示初始化。
		//等super()父类初始化完毕后,才进行子类的成员变量显示初始化。
		//静态代码块->默认初始化->构造函数压栈->super()(注意其中一般方法的覆盖情况)->显示初始化->构造代码块->子类构造函数
		System.out.println("zi cons run..."+num);
		return;
	}
	void show(){
		System.out.println("zi show..."+/*super.*/this.num);//this代表的是本类引用,this所属show函数在子类中,所以是子类对象的引用。
	}
}
class ExtendsDemo6{
	public static void main(String[] args){
		Zi z = new Zi();
		z.show();
	}
}
     由内存图解可以看出,在子类构造函数调用super()访问父类相应构造函数时,super方法中持有了当前子类对象地址的this引用。所以,此时执行父类构造函数中的show()语句的时候,已经出现了覆盖,super方法中的this所指对象是子类对象,调用show方法时,会先去子类方法区中寻找相应show方法。

final关键字

    继承:
    1.好处:提高了代码的复用性。让类与类之间产生了关系,提供了另一个特征多态的前提。
    2.弊端:打破了封装性。
    解决打破封装性的措施:final关键字。
    1.final修饰符:可以修饰类,修饰方法,修饰变量。
    2.final修饰的类不可以被继承。
    3.final修饰的方法不可以被覆盖。(private修饰的方法也不可以被覆盖)
    4.final修饰的变量是一个常量,只能赋值一次。
    当使用的数据不变时,需要定义阅读性强的名称来表示该数据,并将该数据final化。
    被final修饰的变量 名称规范是:所有字母都大写。如果由多个单词组成,需要通过_进行分隔。

/*final*/class Fu{
	/*final*/static  void show(){
		//访问到了系统的内容。
	}
}
class Zi extends Fu{
	public static final int NUM = 4;//全局常量
	static final int x = 7;	//final修饰成员变量,一般还会加上static。
	/*final*/ void show(){
		//
		final double PI = 3.14;//常量
		//PI = 1.23;//编译失败
		System.out.println(PI);//编译成class文件时,PI位置已经被3.14替换。
	}
}
class FinalDemo{
	public static void main(String[] args){
		
	}
}
/*饿汉式,可以用final*/
class Single{
	private static final Single SINGLE_INSTANCE = new Single();
	private Single(){}
	public static Single getInstance(){
		return SINGLE_INSTANCE;
	}
}
/*懒汉式,不可以用final*/
class Single{
	private static Single SINGLE_INSTANCE = null;
	private Single(){}
	public static Single getInstance(){
		if (SINGLE_INSTANCE == null)
			SINGLE_INSTANCE = new Single();
		return SINGLE_INSTANCE;
	}
}

小结
1.super()、this()调用构造函数,不能同时存在第一行,构造函数中只能有其中之一。

   但是如果子类使用的是this(),this()调用的那个本类构造函数还是要有super()。
2.父类中的private属性,子类对象中是存在的。同名时,this、super区分。
3.子父类方法同名、覆盖后,父类中调用原名方法的语句,实际调用的是子类同名方法,已被覆盖。
4.子父类方法中同名属性,子父类构造函数中各自的赋值语句,始终是给本类所属属性赋值。
   (一般子父类中出现同名属性,如不特别注明,一般默认都是this.属性,即操作本类的那个属性)
5.静态方法中,不可以存在super、this,因为静态成员先于对象存在。
6.final修饰类不可被继承,final、private修饰方法不可被覆盖,

   final修饰变量是常量,常量一般都用static final修饰,且大写。
7.覆盖时子类方法权限必须大于等于父类,public不能覆盖private,(权限由小到大:private<默认<public),

   覆盖时子父类方法要么都静态,要么都不静态。
8.抽象父类中的所有抽象方法必须都被非抽象子类覆盖,子类才能实例化。
9.abstract不能和final、private、static共存。


后面一片博客将会介绍,JavaSE面向对象中的抽象类、接口、多态、内部类和匿名内部类。

有任何问题请和我联系,共同进步:lichunchun4.0@gmail.com

转载请声明出处:http://blog.csdn.net/zhongkelee/article/details/44830733

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值