天天看点

黑马程序员--Java基础之面向对象总结(一)

------- android培训、 java培训、期待与您交流! ----------       类和对象的关系:面向对象有三个特征,封装、多继承、多态。现实生活中所面对的都是对象,将对象具体化地描述就需要抽取对象的共性内容,将这些共性的东西组织起来描述一个对象,而在描述一个对象时该对象需要有姓名、年龄、学习功能等。将描述映射到java中时,描述就是class定义的类,具体的对象就是相应的java类在堆内存中用new建立的实体。描述一个事物对象就是在描述它的属性和方法,在java中创建一个类时就需要包含属性和方法,属性对应类中的变量,方法对应类中的函数方法,其实定义类就是在描述事物,在定义它的属性和方法,属性和方法都是类中的成员(成员变量和成员方法)。

//定义Car这个类
class Car {
	// 定义成员变量color车颜色
	String color = "red";
	// 定义成员变量num车轮数量
	int num = 4;

	// 定义成员方法run跑的方法
	void run() {
		System.out.println("color:" + color + "..num=" + num);
	}
}

class CarDemo {
	public static void main(String[] args) {
		// 生产汽车,在java中通过new创建一个对象
		Car c = new Car();// c是创建的对象的名称,它就是一个类类型的变量,记住:类类型变量指向对象
		c.color = "black";// 指挥该对象作用,改变车的颜色,使用方法:对象.对象成员
		c.run();
	}
}
           

      成员变量和局部变量:成员变量和局部变量的作用范围和内存中存储的位置不同。成员变量的作用范围是整个类,类中的所有方法均可以使用,而局部变量的作用范围是函数中或者语句中(for循环中的定义);成员变量存储在堆内存中,因为对象的存在,才在内存中存在,局部变量存储在栈内存中。

public class Book {
	String name;// 成员变量

	void read(int num)// 参数列表中的局部变量
	{ // 函数中的局部变量
		String temp = "书本";
		System.out.println(temp + "包含" + num + "页");
	}
}
           

      匿名对象的使用方法:方法1,当对对象的方法只调用一次时,可以用匿名对象来完成,这样可以简化代码,方法2,将匿名对象当做参数在方法中传递使用。例如:new Car().run(),这是匿名对象的一次调用,这个方法show(Car car)参数是Car类型实体,show(new Car())这样将匿名对象当做方法的参数传递。       封装的概述和特性:封装是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。它的好处有将变化隔离、便于使用、提高重用性、提高安全性,封装的原则是将不需要对外提供的内容都隐藏起来,把属性隐藏起来对外提供公共方法对其访问。私有化是封装的一种表现形式,使用private权限修饰符修饰类中的成员(成员变量和成员函数),被私有的成员只在本类中有效,别的类无权访问。之所以对外提供公共的访问方式,因为可以在访问中加入逻辑判断语句,这样可以提高代码的健壮性。例如:setAge(int age){this.age=age;}可以在里面加上if的逻辑判断,if(age>0&&age<130) this.age=age;这样的判断后才进行age赋值,防止非法赋值。       构造函数的概述:构造函数在对象一创建后便执行,它是对象的初始化,格式为类名(){},例如People(){},在构造函数内部写的代码将会在对象创建时执行,People p=new People();此时p对象创建的时候构造函数将被执行,构造函数可以实现函数的重载,即参数类型、个数、顺序不同的方法重载,例如People(String name,int age){},People p=new People("lisi",23);此时创建对象就调用了带参数的构造函数,它初始化时就给该对象赋值了姓名和年龄。注意点,当类中没有写构造函数时它默认产生一个无参的构造函数,若在类中自定义了构造函数则这个默认的无参构造函数将不存在,举例说明,若类中自定义了People(String name){}等带参数的构造函数,没有People(){}空构造函数,创建对象时若使用空构造函数People p=new People()会报错,除非再加上空构造函数,若类中没有定义任何构造函数,那么它将拥有默认的空构造函数,People p=new People();则是正确的。构造函数和普通函数在写法上有所不同,它是没有返回类型的,在运行方面,构造函数只在对象创建时执行一次, 而普通函数则可能被对象多次调用。

class People {
	private String name;
	private int age;

	// 空构造函数,当类中不存在任何自定义的构造函数时则默认包含People(){}空构造函数
	People() {
		cry();
	}

	// 带一个参数的构造函数,空构造函数方法的重载
	People(String n) {
		this.name = n;
		System.out.println("name=" + name + ",age=" + age);
	}

	// 带俩个参数的构造函数,空构造函数方法的重载
	People(String n, int a) {
		this.age = a;
		System.out.println("name=" + name + ",age=" + age);
	}

	// 普通的函数方法
	public void cry() {
		System.out.println("cry...");
	}

	public static void main(String[] args) {
		// 空构造函数,当类中不存在任何构造函数则默认拥有空构造函数,这样是正确的
		// 但是当类中有自定义构造函数并没有空构造函数,这样是错误的
		People p = new People();
		// 不同对象的构造函数初始化
		People p1 = new People("张三");
		People p2 = new People("李四", 23);
		p.cry();//调用对象的普通方法
	}
}
           

      构造代码块和构造函数的区别:形式不同,构造代码块仅仅是一对花括号{},它也是给对象进行初始化;优先级不同,构造代码块的优先级高于构造函数,一个类对象被创建后,它先执行构造代码块的内容,在执行构造函数;性质不同,构造代码块是对所有对象的统一初始化,即对不同对象共性的初始化,而构造函数由于可能重载了很多方法,它是对不同对象进行相应的初始化。       this关键字的概述:this代表它所在函数所属对象的引用,简单来说,哪个对象在调用this所在的函数,this就代表哪个对象。People(String name){this.name=name},这个构造方法就是,对象初始化时将赋值的局部变量name再赋值给该对象的name成员属性。构造函数间的调用只能用this语句,例如:People(String name){},People(String name,int age){ this(name);this.age=age;}后者俩个参数的构造函数调用前面一个参数的构造函数,这里必须使用this来调用,同时调用的构造函数有参数也要给它传递参数。注意,this语句只能在构造函数的第一行,因为初始化要先执行。       static关键字的概述:static是一个修饰符,用于修饰成员(成员变量、成员函数),当成员被static修饰后除了能使用对象调用,还可以被类名直接调用,类名.静态成员。static的特点,它随着类的加载而加载,优先于对象存在,被所有对象所共享,可以直接被类名调用。实例变量和类变量的区别:第一是存放位置不同,类变量随着类的加载而存于方法区中,实例变量则是随着类对象的建立而存在于堆内存中。第二是生命周期不同,类变量的生命周期最长,随着类的消失而消失,实例变量则随着对象的消失而消失。对于static修饰的方法就是静态方法,静态方法有俩个注意事项,一是静态方法只能访问静态成员,而非静态方法可以访问静态和非静态成员,二是静态方法中不可以定义this,super关键字,因为静态优先于对象存在,该静态方法加载时是不存在this对象的。静态有利有弊,好处是对象将共享的数据存放到单独的空间,不是建立一个对象就在堆内存中开辟相应空间,这样节省了内存空间,坏处是生命周期过长,访问具有局域性(静态方法内只能访问静态成员)。

class Person {
	private String name;// 实例变量
	private static String country = "Chnia";// 类变量

	public static void show() {// 静态方法show,静态方法中只能存在静态成员
		System.out.println("..." + country);
	}

	public void speak() {// 非静态方法能存在静态和非静态的成员
		show();
		System.out.println("country:" + country + ",name:" + this.name);
	}

	public static void main(String[] args) {
		Person p = new Person();
		p.show();// 静态方法可以被对象调用
		Person.show();// 也可以使用类名直接调用静态成员
	}
}
           

      静态的应用工具类:静态一般用于所有对象访问共享的数据,它可以节约内存,使用类名直接调用时很方便,对于某些操作我们需频繁使用一些方法时,我们可以将它们封装在一个类中,把它当做一个工具类,别的类方法可以直接调用这个工具类。由于工具类里都是封装的各种静态方法以便调用,没有自己的成员变量,所以该类也没有必要被实例化,将构造方法私有化后,就使其只能用类名调用方法且不能实例化。

class Tool {// 定义工具类
	private Tool() {
	}// 私有化构造方法使其不能被实例化

	public static int getSum(int a, int b) {// 静态的求和方法
		return a + b;
	}
}

class ToolDemo {
	public static void main(String[] args) {
		Tool.getSum(5, 6);// 使用类名直接调用方法
	}
}
           

      帮助文档的制作:使用的注释方法对类、类方法进行注释,注释时@author是作者,@version是版本,@param是方法的参数,@return是返回值,这几个是比价常用的注释参数,具体详细的可以查找文档了解,当一个类文件完成注释后,再使用javadoc工具的参数来执行该文件,就可以将其产生该类的帮助文档,例如:javadoc -c(制定存储盘符) myhelp(制定目录) -author -version Tool.java,这里注意,默认生成帮助文档时是不包含auther和version信息的,必须手动加上。帮助文档生成时只生成public或者protected修饰的方法、构造函数,同时类是什么修饰符则默认的构造函数就是什么修饰符,若是自定义构造方法则按照它本身的修饰符。       静态代码块的概述:静态代码块随着类的加载而加载,并且只加载一次,格式为:static{},StaticDemo c=null,此时只有定义变量,并没有具体的对象引用,所以对象还没有被使用,static代码块的方法不执行,StaticDemo c=new StaticDemo(),new StaticDemo()这俩种都使用了该类,所以static代码块被执行一次,并且只执行一次。       静态代码块、构造代码块和构造函数的区别:静态代码块格式是static{},类加载时只加载一次,构造代码块的格式是{},类构造时对该类的初始化,初始化内容所有类对象共享,构造函数的格式是类名(){},类构造时对该类相应构造对象的初始化,初始化内容只对应相应的构造对象。它们三者的执行顺序是,先实行静态代码块,再执行构造代码块,最后执行构造函数,静态代码块和构造代码块若有多个则在原来的顺序基础上再按从上到下的顺序执行。       对象初始化过程:Person p=new Person(),首先第1步jvm将Person加载进内存,这时栈空间先开辟一个main静态方法的空间,里面有一个变量p,第2步执行static静态代码块,第3步接着在堆内存开辟一个空间对象,new Person(),分配内存地址,第4步建立对象的特有属性并进行默认的初始化(int a=3时a默认初始化为0),第5步对属性进行显性初始化(显性初始化a=3),第6步构造代码块初始化,第7步构造函数初始化,第8步将内存地址赋给栈内存的p变量。       对象调用成员过程:Person p=new Person("zhnagsan",23);p.setName("lisi");Person.show(),首先第1步在对象开辟空间前,static静态代码块执行后,内存中的方法区先加载类中的静态属性,第2步加载类中的静态方法,第3步加载类中的非静态方法,第4步由于setName(String name)方法的参数是局部变量所以在栈内存中开辟空间setName方法的空间,第5步方法中的this是指调用该方法的对象,p的地址被赋给this引用,第6步方法执行结束该方法空间被释放,第7步方法区直接调用里面的静态方法,若方法中调用了静态属性,则使用方法区中的静态属性。       上网查阅后类加载的新理解:先加载静态方法,再加载静态变量和静态代码块(按出现顺序从上往下),然后在堆内存中开辟空间,加载成员变量和成员方法(按出现顺序从上往下),接着构造代码块初始化,最后构造函数初始化。       单例设计模式:单例设计模式有两种,一种是饿汉式,它是Single类一加载就创建好了对象,一种是懒汉式,Single类加载后还没有创建对象,只当调用getInstance方法时对象才被创建。开发中多常用饿汉式,代码少并且线程安全,懒汉式线程不安全,需要加上线程锁进行优化,相应代码优化后比较复杂。

class Single1 {// 饿汉式
	private static Single1 single = new Single1();// 类加载就创建对象

	private Single1() {
	}// 构造方法私有化后不能实例化创建

	public static Single1 getInstance() {
		return single;// 只能过该静态方法获得该对象
	}
}

class Single2 {// 懒汉式
	private static Single2 single = null;// 类加载时对象尚未创建

	private Single2() {
	}

	public static Single2 getInstance() {
		if (null == single) {// 执行获取对象方法时,先判断该对象是否为空,不为空则直接返回对象,此处减少了很多次同步锁的判断
			synchronized (Single2.class) {// 当对象未创建进行创建,判断同步锁再锁住该方法块
				if (null == single) {// 创建对象时进行必要的非空检查,保证程序只创建一次对象
					single = new Single2();
				}
			}
		}
		return single;// 判断对象已创建的情况下永远返回已创建的那个唯一对象
	}
}
           

       继承的概述:继承是面向对象的第二大特性,面向对象的三大特性是封装、继承、多态。继承的优点:一是提高了代码的复用性,二是让类与类之间有了关系,正因为这个关系才有了多态这个特性。注意,不要为了获取其它类的功能,简化代码而继承,继承一定要是类与类之间有所属关系才可以继承。所属关系is a。在java语言中只支持单继承,不支持多继承,打个比方说,一个孩子只允许有一个父亲,这个孩子继承于这个父亲。多继承会带来安全隐患,所以没有多继承:当多个父类定义了相同的功能时,子类多继承这个几个父类,若这几个功能内容都不同时,子类调用相应方法时不知道该调用哪个父类的方法功能。java里使用多实现来表示多继承这种形式,即后面将会学习到的接口。java支持多层继承,它就是一个继承体系,使用继承体系的方法是先查阅父类定义的功能,然后再创建最子类对象进行使用。       子父类中变量的特点:如果子类中出现父类中非私有成员变量时,子类访问本类中的成员变量使用this,访问父类中的同名成员变量使用super。super和this的使用几乎一致,this代表对本类对对象的使用,super代表父类对象的使用。       子父类中函数的特点覆盖:当子类中出现和父类一摸一样的函数时,若子类对象调用该函数则会运行子类中的函数,类似于父类中的函数被覆盖了。重写(覆盖)是函数的另一个特性。覆盖时要注意,子类覆盖父类,必须保证子类方法的权限大于父类,否则编译失败,并且静态只能覆盖静态方法。

public class Person {// 父类Person
	String name;// 不是私有子类也拥有此属性并使用

	public void speak()// 父类共有方法speak
	{
		System.out.println("person speak");
	}

	void show() {// 父类默认权限方法show
		System.out.println();
	}
}

class Student extends Person {// 子类Student继承于父类Person
	public void speak() {// 重写父类speak方法,方法名、返回值必须一样,内容不同
		System.out.println(name + "student speak");// 使用父类属性name,默认为super.name,若子类也定义name,则默认为this.name
	}

	public void show() {// 重写父类show方法,重写父类方法必须权限大于父类的,否则编译失败
		System.out.println("student show");
	}
}
           

      子类实例化过程:当子类继承父类后,父类的数据可以和读取,因此在子类建立时需要先访问父类的构造函数,查看父类的数据是如何初始化的,子类默认访问父类的空构造函数,若需要访问别的构造函数则用super语句来指定。注意,super语句必须在构造函数的第一行。子类实例化过程:子类的所有构造函数都要默认访问父类的空构造函数,因为每个子类的构造函数第一行都有一个隐式的super()语句来访问父类的构造函数,当父类中没有空构造函数时必须手动使用super语句指定访问父类中存在的构造 函数,同时子类的构造函数第一行也可以手动使用this语句访问本类的构造函数,但是本类至少有一个构造函数会访问到父类的构造函数。子类中的构造函数第一行只能存在this或者super语句,俩者不可同时存在。       final关键字的概述:最终的意思,它是一个修饰符。一它可以修饰类、函数、变量,二被final修饰的类不可以被继承,它是为了父类避免被继承,被子类复写功能,三被final修饰的方法不可以被复写,四被final修饰的变量是一个常量,只能被赋值一次,既可以修饰成员变量也可以修饰局部变量,五内部类定义在局部变量位置上时,只能访问该局部被final修饰的局部变量。       抽象类的概述:当多个类出现相同的功能,但是功能的主体不同,这时可以进行向上抽取,这时只需要抽取该功能的定义,而不需要抽取方法主体,这种类叫做抽象类。抽象类的特点:第一抽象方法一定在抽象类中,第二抽象方法和抽象类必须由abstract关键字修饰,第三抽象类不可以使用new关键字创建对象,因为调用抽象方法是没有意义的,第四抽象类中的抽象方法要被使用,必须由子类复写所有的抽象方法后,再由子类创建对象调用,如果子类只复写了部分抽象方法,那么该子类还是一个抽象类。抽象类和普通类的区别,抽象类比普通类多了只可以定义名称而不能实现的抽象方法,抽象类不可以实例化,抽象类可以不包含抽象方法,若这样做仅仅是让该类不可以建立对象。       模板方法模式:在定义功能时,功能的一部分是确定的,但是有一部分不确定,而确定的部分在使用不确定的部分,那么就可以将这不确定的部分暴露出去,由子类去完成。这个父类的不确定方法就当成一个模板方法,它可以实现或者不实现,不实现时定义成抽象方法让子类去实现,实现时让子类去重写,子类继承它后使用这个模板方法可能会有自己不同的代码实现。

abstract class GetTime {// 抽象的父类
	public final void getTime() {// 不暴露给子类,只能使用该方法
		long start = System.currentTimeMillis();// 获取系统的当前时间,类型为长整形
		Runcode();// 调用了不确定的模板方法
		long end = System.currentTimeMillis();
		System.out.println();
		System.out.println("相差时间为:" + (end - start));// 输出代码执行后的时间差
	}

	public abstract void Runcode();// 暴露的模板方法,可以实现或不实现
}

class SubTime extends GetTime {// 子类继承父类
	@Override
	public void Runcode() {// 实现了父类暴露的抽象方法,由子类自己具体实现
		for (int i = 0; i < 1000; i++) {// 循环输出使程序运行一段时间
			System.out.print(i);
		}
	}
}

class Test {
	public static void main(String[] args) {
		GetTime time = new SubTime();// 实现该类,抽象类不能实例化,只能由子类实例化
		time.getTime();// 调用父类的方法,其中调用的Runcode方法是子类自己实现的方法,因为子类中定义或重写了该方法
	}
}