黑马程序员——Java 面向对象(下)

------- android培训java培训、期待与您交流! ----------


Java面向对象(高级)


抽象类(abstract class)


在Java中可以创建一种类专门用来当作父类,这种类称之为“抽象类”。抽象类的作用类似“模板”,其目的是要设计者依据它的格式来修改并创建新的类,但是并不能直接有抽象类创建对象,只能通过抽象类派生出新的类,再有它来创建对象,抽象类也是单继承的,即一个子类只能继承一个抽象类。

1.1 抽象类和抽象方法的定义?

抽象类(abstract class):使用了abstract关键字所修饰的类叫做抽象类。抽象类无法实例化也就是说,不能new出来一个抽象类的对象(实例)。

抽象方法(abstract method):使用abstract关键字所修饰的方法叫做抽象方法。抽象方法需要定义在抽象类中。相对于抽象方法,之前所定义的方法叫做具体方法(有声明,有实现)。

抽象类的使用规则如下:

a).包含抽象方法的类必须是抽象类;

b).抽象类和抽象方法都要使用abstract关键字声明;

c).抽象方法只需要声明而不需要实现;

d).抽象类必须被子类继承,子类(如果不是抽象类)必须覆写抽象类中的全部抽象方法。

如何判断一个类是否是抽象类?

1.如果一个类包含了抽象方法,那么这个类一定是抽象类。

2.如果某个类是抽象类,那么该类可以包含有具体方法。

3.如果一个类中包含了抽象方法,那么这个类一定要声明成abstract class,也就是说,该类一定是抽象类;反之,如果某个类是抽象类,那么该类既可以包含抽象方法,也可以包含具体方法。

无论何种情况,只要一个类是抽象类,那么这个类就无法实例化。在子类继承父类(父类是抽象类)的情况下,那么该子类必须要实现父类中所定义的所有抽象方法,否则,该子类需要声明成一个抽象类(abstract class)。

定义一个抽象类AbstractA,定义一个类AbstractB继承抽象类AbstractA:

abstract class AbstractA //定义抽象类
{
	String name = "zhangsan";
	public abstract void print();//定义抽象方法
}
//继承抽象类
class AbstractB extends AbstractA
{
	public void print(){
		System.out.println("姓名:"+super.name);
	}
}
public class Demo
{
	public static void main(String[] args){
		AbstractB b = new AbstractB();
		b.print();
	}
}

抽象类AbstractA和类 AbstractB的关系如图:



1.2 在抽象类中可以定义构造方法?

在一个抽象类中是允许存在构造方法的,因为抽象类依然使用的是类继承关系,而且抽象类中也存在各个属性,所以子类在实例化之前必须先对父类进行实例化(构造方法)。

在抽象类中定义构造方法:

public AbstractA(){//定义构造方法
		System.out.println("执行--> A、抽象类AbstractA的构造方法。");
	}
public AbstractB(){//定义构造方法
		System.out.println("执行--> B、类AbstractB的构造方法。");
	}

输出运行结果:

---------- java ----------
执行--> A、抽象类AbstractA的构造方法。
执行--> B、类AbstractB的构造方法。
姓名:zhangsan

从上面可以看出,抽象类中定义了抽象方法,但是定义的抽象方法并不能直接被外部调用,在子类对象实例化前也同样会默认调用父类中无参构造方法,也就是说此时的子类实际上也隐含了一个super关键字调用构造方法的语句:

class AbstractB extends AbstractA
{
	public AbstractB(){//定义构造方法
		super();   //隐藏了此语句
		System.out.println("执行--> B、抽象类AbstractB的构造方法。");
	}
	public void print(){
		System.out.println("姓名:"+super.name);
	}
}

抽象类与普通类:实际上抽象类就是比普通类多定义了一个抽象方法,除了不能直接进行对象的实例化操作之外并没有任何的不同。抽象类中的抽象方法不能使用private修饰。



接口 (Interface)

接口是Java中最重要的概念之一,它可以被理解为一个特殊的类,是由全局常量和公共的抽象方法所组成。

接口的地位等同与class,接口中的所有方法都是抽象方法。在声明接口中的方法时,可以使用abstract关键字,也可以不使用,通常省略掉。

1.如何定义接口?

使用interface关键字所修饰的接口,就叫做接口。需要注意的是,在接口中的抽象方法必须定义为public访问权限。也可以不写public。默认不是default,而是public。

interface InterfaceA
{
	public static final String AUTHOR="zhangsan";//定义全局常量
	public abstract void print();				 //定义抽象方法
	public abstract String getInfo();			 //定义抽象方法
}

也可以写如下格式:

interface InterfaceA
{
	String AUTHOR="zhangsan";//定义全局常量
	void print();		//定义抽象方法
	public String getInfo();//定义抽象方法
}

2. 实现接口 (implements)

类可以实现接口实现使用关键字(implements)表示,代表了某个类实现了某个接口。一个类实现了某个接口,那么该类必须要实现接口中声明的所有方法。如果该类是个抽象类,那么就无需实现接口中的方法了。

interface A
{
	String AUTHOR="zhangsan";//定义全局常量
	public String getInfo(); //定义抽象方法
}
interface B
{
	void print();			//定义抽象方法
}
class C implements A,B
{
	public String getInfo(){
		return "Hello "+AUTHOR;
	}
	public void print(){
		System.out.println("实现了接口中的抽象方法。");
	}
}
public class Demo
{
	public static void main(String[] args){
		C c = new C();
		c.print();
		System.out.println(c.getInfo());
	}
}

 

输出运行结果:

---------- java ----------
实现了接口中的抽象方法。
Hello zhangsan

由上面的例子可以看出,一个类同时实现了两个接口。Java是单继承的,也就是说某个类只能有唯一一个父类,这样在子类中就必须同时要覆写两个接口中的全部方法。如果子类在实现某个接口的同时又继承了某个(抽象)类,那么子类同样必须覆写父类或接口中的抽象方法。

abstract class D
{
	public abstract void sayHello();
}
class C extends D implements A,B
{
	public void sayHello(){
		System.out.println("实现了抽象类中的抽象方法。");
	}
	public String getInfo(){
		return "Hello "+AUTHOR;
	}
	public void print(){
		System.out.println("实现了接口中的抽象方法。");
	}
}

输出运行结果:

---------- java ----------
实现了接口中的抽象方法。
实现了抽象类中的抽象方法。
Hello zhangsan


3. 接口的继承

一个接口可以继承多个父接口。如 interface 子接口 extends 父接口A,父接口B,......

interface A
{
	String AUTHOR="zhangsan";//定义全局常量
	public String getInfo(); //定义抽象方法
}
interface B
{
	void print();			//定义抽象方法
}
//实现接口A,B
interface C extends A, B
{
	public void printC();
}
class X implements C
{
	public void printC(){
		System.out.println("实现了多个接口。");
	}
	public String getInfo(){
		return "Hello "+AUTHOR;
	}
	public void print(){
		System.out.println("实现了接口中的抽象方法。");
	}
}

输出运行结果:

---------- java ----------
实现了接口中的抽象方法。
实现了多个接口。
实现了抽象类中的抽象方法。
Hello zhangsan


抽象类与接口总结:

1.抽象类:包含一个抽象方法的类称为抽象类,抽象类必须有子类,子类要覆写全部的抽象方法。
2.接口:由抽象方法和全局常量组成的特殊类,一个类可以实现多个接口,接口可以实现多继承。

抽象类与接口区别与联系




多态 (Polymorphism)续

多态是面向对象程序设计的重要特征。多态是允许程序中出现重名的现象。Java语言中含有方法重载与对象多态两种形式的多态。

1).方法重载:在一个类中,允许多个方法使用同一个名字,但是方法的参数不同,完成的功能也不同。

2).对象多态:子类对象可以与父类对象进行相互转换,而且根据其使用的子类的不同,完成的功能也不同。

多态的特性使程序的抽象程度和简捷程度更高,有助于程序员对程序的分组协同开发。


前面说过多态,就是父类型的引用可以指向子类型的对象。在这里增加了接口类型的引用指向实现该接口的类的实例。关于接口与实现接口的类之间的强制类型转换方式与父类和子类之间的强制类型转换方式一样。

interface AA{
	public void output();
}
class BB implements AA{
	public void output(){
		System.out.println("BB");
	}
}
public class Demo{
	public static void main(String[] args)
	{
		//向上类型转换
		BB b1 = new BB();
		AA a1 = b1;
		b1.output();
		//向下类型转换
		AA a2 = new BB();
		BB b2 = (BB)a2;
		b2.output();
	}
}



 

设计一个方法,要求此方法接受Car类的任意子类对象,并调用方法。


class Car{
	public void run(){
		System.out.println("car is running");
	}
}
class BMW extends Car{
	public void run(){
		System.out.println("BMW is running");
	}
}
class QQ extends Car{
	public void run(){
		System.out.println("QQ is running");
	}
}


此时如果不是使用对象多态。代码如下:


public class Demo{
	public void run(BMW bmw){
		bmw.run();
	}
	public void run(QQ qq){
		qq.run();
	}
	public static void main(String[] args)
	{
		Demo test = new Demo();

		BMW bmw = new BMW();
		test.run(bmw);

		QQ qq = new QQ();
		test.run(qq);
	}
}

输出运行结果:

---------- java ----------
BMW is running
QQ is running


 

以上程序虽然实现了基本的要求,但是可以发现:如果按照以上的方式完成,则当产生了一个Car类的子类时,run()方法就要重载一次,这样扩充功能的话,必须修改类本身。那么采用对象多态呢?


public class Demo{
	public void run(Car car){
		car.run();
	}
	public static void main(String[] args)
	{
		Demo test = new Demo();
		Car car = new BMW();
		test.run(car);
		QQ qq = new QQ();
		test.run(qq);
	}
}

此时结果也是一样,但是程序的可扩展性更好了。在run()方法中使用了对象的多态性,所有可以接收任意的子类对象,无论子类如何增加,run()方法都不会做出任何修改,因为一旦发生对象的向上转换关系后,调用的方法一定是被子类覆写过的方法。

static 关键字


如果使用一个类分别开辟栈内存及堆内存,在堆内存重要保存对象中的属性,每个对象有自己的属性。如果现在有些属性希望被所有对象共享,则必须将其声明为static属性。如果一个类中的方法想由类直接调用,则可以声明为static方法。

1. static 修饰属性

static声明属性,则此属性称为全局属性(静态变量)。无论一个类生成了多少个对象,所有这些对象共同使用唯一一份静态的成员变量;一个对象对该静态成员变量进行了修改,其它对象的该静态成员变量也会随之发生改变。如果一个成员变量是static的,那么我们可以通过"类名.成员变量名"的方式来使用它(推荐)。

有下面例子,不使用static修饰成员变量的效果:

class Child {
	private String name ;
	private int age ;
	String sugar = "棒棒糖" ;
	public Child(String name,int age){
		this.name = name ;
		this.age = age ;
	}
	public String eat(){
		return "姓名:" + this.name + ",年龄:" + this.age + ",糖:" + sugar ;
	}
}
public class Demo{ 
	public static void main(String[] args){
		Child per1 = new Child("张三",10) ;
		Child per2 = new Child("李四",11) ;
		Child per3 = new Child("王五",12) ;
		System.out.println(per1.eat()) ;
		System.out.println(per2.eat()) ;
		System.out.println(per3.eat()) ;
		System.out.println("---------------------") ;
		per1.sugar = "棉花糖" ;
		per2.sugar = "棉花糖" ;
		per3.sugar = "棉花糖" ;
		System.out.println(per1.eat()) ;
		System.out.println(per2.eat()) ;
		System.out.println(per3.eat()) ;
	}
}


输出运行结果:

---------- java ----------
姓名:张三,年龄:10,糖:棒棒糖
姓名:李四,年龄:11,糖:棒棒糖
姓名:王五,年龄:12,糖:棒棒糖
---------------------
姓名:张三,年龄:10,糖:棉花糖
姓名:李四,年龄:11,糖:棉花糖
姓名:王五,年龄:12,糖:棉花糖

Output completed (0 sec consumed) - Normal Termination


由上可以发现很明显的问题,当小朋友换糖吃的时候,小朋友越多,产生的Child对象就越多。按照内存分配来讲,每个Child对象都单独占着各自的sugar属性。



class Child {
	private String name ;
	private int age ;
	static String sugar = "棒棒糖" ;//使用static修饰,有默认值
	public Child(String name,int age){
		this.name = name ;
		this.age = age ;
	}
	public String eat(){
		return "姓名:" + this.name + ",年龄:" + this.age + ",糖:" + sugar ;
	}
}
public class Demo{ 
	public static void main(String[] args){
		Child per1 = new Child("张三",10) ;
		Child per2 = new Child("李四",11) ;
		Child per3 = new Child("王五",12) ;
		System.out.println(per1.eat()) ;
		System.out.println(per2.eat()) ;
		System.out.println(per3.eat()) ;
		System.out.println("---------------------") ;
		per1.sugar = "棉花糖" ;
		System.out.println(per1.eat()) ;
		System.out.println(per2.eat()) ;
		System.out.println(per3.eat()) ;
	}
}

 

输出运行结果:

---------- java ----------
姓名:张三,年龄:10,糖:棒棒糖
姓名:李四,年龄:11,糖:棒棒糖
姓名:王五,年龄:12,糖:棒棒糖
---------------------
姓名:张三,年龄:10,糖:棉花糖
姓名:李四,年龄:11,糖:棉花糖
姓名:王五,年龄:12,糖:棉花糖


现在只修改了一个对象per1的sugar属性,其余对象的sugar属性内容随之发生改变。说明使用static声明的属性是所有对象共享的。



Java中常用的内存区域:

1).栈内存空间:保存所有的对象名称(更准确地说保存了引用的堆内存空间地址);

2).堆内存空间:保存每个对象的具体属性内容;

3).静态数据区:保存static类型的属性;

4).全局代码区:保存了所有方法的定义。

一个类中的公共属性现在由一个对象修改,这样操作不推荐使用。类的公共属性应该由类进行修改最合适的。因为我们不知道一个类到底会产生多少个对象。所以上面访问static属性最好改为类名称直接调用。我们也把使用static修饰的属性称之为类属性。

Child.sugar="棉花糖";

2. static 声明方法

static修饰方法叫做静态方法也可以称之为“类方法”。对于静态方法来说,可以使用“类名.方法名”的方式来访问。使用static声明方法:

class Child {
	private String name ;
	private int age ;
	static String sugar = "棒棒糖" ;//使用static修饰,有默认值
	public Child(String name,int age){
		this.name = name ;
		this.age = age ;
	}
	public static void setSugar(String s){
	sugar = s;
	}
	public String eat(){
		return "姓名:" + this.name + ",年龄:" + this.age + ",糖:" + sugar ;
	}
}



这样就可以使用“类名.方法名”的方式来调用静态方法setSugar,并且可以修改其值。

Child.setSugar("泡泡糖");

 

注意:在使用static声明方法中,是不能调用非static类型声明的属性或方法的,非static声明的方法可以调用static声明的属性或方法。因为在程序中所有的属性和方法必须在对象实例化之后才可以调用,而static类型的方法在对象未被实例化时就可以被类所调用。

不能在静态方法中使用this关键字。静态方法只能继承,不能重写(Override)。子类可以定义于父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法(满足覆盖约束),而且Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。父类的非静态方法不能被子类覆盖为静态方法。 总之,静态的不能访问非静态的,静态的可以访问静态的,非静态的可以访问静态的。

 

3.static 代码块

所谓的代码块就是指用“{ }”括起来的一段代码,根据位置不同,代码块可分为普通代码块、构造块、静态代码块、同步代码块4种。

3.1 普通代码块

 

public class CodeDemo 
{
	public static void main(String[] args) 
	{
		{	//定义一个普通代码块
			int i = 100;//局部变量 i
			System.out.println("普通代码块 --> " + i);
		}
		int i = 50 ; //与局部变量名称相同
		System.out.println("代码块外   --> "+ i);
	}
}


输出运行结果:

---------- java ----------
普通代码块 --> 100
代码块外   --> 50

3.2 构造代码块

构造代码块是直接写在类中的代码块。

class Demo
{
	{   //定义构造块
		System.out.println("1. 构造块");	
	}
	public Demo(){
		System.out.println("2. 构造方法");
	}
}
public class CodeDemo 
{
	public static void main(String[] args) 
	{
		new Demo();
		new Demo();		
		new Demo();
	}
}


输出运行结果:

---------- java ----------
1. 构造块
2. 构造方法
1. 构造块
2. 构造方法
1. 构造块
2. 构造方法


从程序的结果可以看出,构造块优先与构造方法执行而且每次实例化对象是都会执行构造块中的代码,会执行多次。

3.3 静态代码块

静态代码块是使用static关键字声明的代码块。

 

class Demo
{
	{   //定义构造块
		System.out.println("1. 构造块");	
	}
	static
	{   //定义静态代码块
		System.out.println("0. 静态代码块");	
	}
	public Demo(){
		System.out.println("2. 构造方法");
	}
}
public class CodeDemo 
{
	static{
		//在主方法所在类中定义静态代码块
		System.out.println("在主方法所在类中定义静态代码块。");	
	}
	public static void main(String[] args) 
	{
		new Demo();
		new Demo();		
		new Demo();
	}
}


输出运行结果:

---------- java ----------
在主方法所在类中定义静态代码块。
0. 静态代码块
1. 构造块
2. 构造方法
1. 构造块
2. 构造方法
1. 构造块
2. 构造方法


可以看出,静态代码块优先于主方法执行,而在类中定义的静态代码块优先于构造块执行,而且不管产生多少对象,静态代码块只会执行一次。

static代码块:静态代码块的作用,主要完成一些初始化工作,首先执行静态代码块,然后执行构造方法。静态代码块在类被加载的时候执行,而构造方法是在生成对象的时候执行;要想调用某个类来生成对象,首先需要将类加载到Java的虚拟机(JVM)上,然后由JVM加载这个类来生成对象。静态代码块只会执行一次,是在类被加载的时候执行的,因为每个类只会被加载一次,所以静态代码块也只会执行一次;而构造方法则不然,每次生成一个对象的时候都会调用类的构造方法,所以new一次就会调用构造方法一次。

 

static静态代码块的妙用:静态代码块优先于主方法执行,那么就可以直接使用静态代码块,而不是用主方法。

public class CodeDemo
{
	static{
		System.out.println("HELLO");
		System.exit(1);//直接退出JVM,就可以避免程序寻找主方法
	}
}

 

 

final 关键字

 

final在Java中表示的是最终的,决定性的,不可更改的。可以使用final关键字声明类,方法、属性。在声明时需要注意以下几点:

a).使用final声明类不能有子类;

b).使用final声明方法不能被子类所覆写;

c).使用final声明的变量是常量,常量是不可更改的。
 

final class A{}
class B extends A {} //错误

final class 不能被继承

class G{
 public final void output(){
  System.out.println("G");
 }
}
class H extends G{
 public void output(){
  System.out.println("H");
 }
}

//错误信息:

---------- javac ----------
 H 中的 output() 无法覆盖 G 中的 output();被覆盖的方法为 final
	public void output()
	            ^
1 错误

final method不能被覆写

class A {
	public final String INFO = "hello" ;
	public void fun(){
		INFO = "word" ;//不能修改
	}
}

//错误信息:

---------- javac ----------
 无法为最终变量 INFO 指定值
      INFO = "word" ;//不能修改   
      ^
1 错误

 

final声明的变量就是常量,不能更改。

注意:当final修饰一个原生数据类型时,表示该原生数据类型的值不能发生改变(比如说不能把10变为20);如果final修饰一个引用类型时,表示该引用类型不能再指向其他对象了,但是该引用所指向的对象的内容是可以发生变化的。

class People{
	final int age = 10;
	final Address address = new Address();
}
class Address{
	String name = "beijing";
	public void output(){
		System.out.println(this.name);
	}
}
public class CodeDemo{
	public static void main(String[] args)
	{
		People people = new People();
		//people.age = 20;
		//people.address = new Address();//错误,该引用不能指向其他新的对象了
		people.address.name = "shanghai";
		people.address.output();
	}
}

 

对于final类型成员变量,一般有两种赋初值方式:

1).在声明final类型的成员变量是就赋上初值;

2).在声明final类型的成员变量时不赋初值,但是在类的所有构造方法都为其赋上初值。

public class Demo{
	//final int a = 11;//声明是赋初值
	final int a ; //在构造方法中赋值
	public Demo(){
		a = 0;
	}
	public Demo(int a){
		this.a = a;
	}
}


 

小结在接口中所声明的方法都是抽象方法。接口中的方法都是public的。在接口中也可以定义成员变量,接口中的成员变量都是public、final、static的。一个类不能即是final,又是abstract的。因为abstract的主要目的是定义一种约定,让子类去实现这种约定,而final表示该类不能被继承,这样二者就相互矛盾。因此一个类即不能是final的,又是abstract的。

 

单例设计模式(singleton design) 

单例模式单例模式是一种常见的设计模式,单例模式分三种:

懒汉式单例、饿汉式单例、登记式单例三种。
 
单例模式有一下特点:
1).单例类只能有一个实例。
2).单例类必须自己自己创建自己的唯一实例。
3).单例类必须给所有其他对象提供这一实例。


1.懒汉式单例在类被加载的时候,唯一实例已经被创建。这个设计模式在Java中容易实现,在别的语言中难以实现。

class LazySingleton {
   //私有静态对象,加载时候不做初始化
   private static LazySingleton m_intance=null;
   //私有构造方法,避免外部创建实例
   private LazySingleton(){
   }
   //静态工厂方法,返回此类的唯一实例. 当发现实例没有初始化的时候,才初始化
   synchronized public static LazySingleton getInstance(){
        if(m_intance==null){
           m_intance=new LazySingleton();
        }
		return m_intance;
    }
}
public class Demo{
    //测试一下单例模式
	public static void main(String[] args){
		LazySingleton lazySingleton1 = LazySingleton.getInstance();
		LazySingleton lazySingleton2 = LazySingleton.getInstance();
		if(lazySingleton1==lazySingleton1){
			System.out.println("同一个对象实例");
		}else{
			System.out.println("不是同一个对象实例");
		}
	}
}


 

2.饿汉式单例在类加载的时候不创建单例实例。只有在第一次请求实例的时候的时候创建,并且只在第一次创建后,以后不再创建该类的实例。

//单例模式-饿汉式单例  
class HungrySingleton {       
    //私有的(private)唯一(static final)实例成员,在类加载的时候就创建好了单例对象  
    private static final HungrySingleton h_instance = new HungrySingleton();  
    //私有构造方法,避免外部创建实例        
    private HungrySingleton() {
	//提供了一个空的构造方法
	}     
    //静态工厂方法,返回此类的唯一实例.       
    public static HungrySingleton getInstance() { 
       return h_instance;      
    } 
}  
public class Demo{
	public static void main(String[] args){
		//下面来判断一下有没有达到单例效果
		HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();   
		HungrySingleton hungrySingleton2 = HungrySingleton.getInstance(); 
		
		if(hungrySingleton1==hungrySingleton2)){
			System.out.println("同一个对象实例"); 
		}else{  
			System.out.println("不是同一个对象实例"); 
		}
	}
} 


3.登记式单例(略)

Object 类

 

在定义一个类的时候,如果没有显示指定该类的父类,那么该类就会默认继承于java.lang.Object(JDK提供的一个类,Object类是Java中所有类的直接或间接父类)。在Java中实际上一切都是属于继承关系。

以下两种类的定义是一样的:

class Person
{
}
class Person extends Object
{
}

 

在Object类中定义了许多方法,提供给我们在开发中使用。主要有以下几个方法:

1).public boolean equals(Object obj),表示要与之比较的引用对象obj与当前对象是否相等。

2).public String toString(),返回该对象的字符串表示。

3).public int hashCode(),返回该对象的哈希码值。

4).public final Class<?> getClass(),返回此 Object 的运行时类。返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。


Object的toString()方法:

class ObjectDemo
{
}
public class Demo
{
	public static void main(String[] args){
		ObjectDemo demo = new ObjectDemo();
		System.out.println("直接输出:"+ demo);
		System.out.println("调用toString()方法:"+demo.toString());
	}
}


输出运行结果:

---------- java ----------
直接输出:ObjectDemo@c17164
调用toString()方法:ObjectDemo@c17164


重写Object类的toString()方法:

class Person
{
	private String name;
	private int age;
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	//重写Object类的toString方法
	public String toString(){
		return "姓名:"+this.name+",年龄:"+this.age;
	}
}
public class Demo
{
	public static void main(String[] args){
		Person p1 = new Person("张三",22);
		System.out.println("输出对象信息:"+ p1);
	}
}


输出运行结果:

输出对象信息:姓名:张三,年龄:22

在Person类中重写了Object类中的toString方法,这样直接输出对象时调用的是被子类重写过的toString方法

Object类中的equals()方法的功能就是对象的比较,如果一个类要实现对象的比较,则直接在类中重写equals()方法即可。

class Person
{
	private String name;
	private int age;
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	//重写equals方法
	public boolean equals(Object obj){
		if (this == obj){
			return true;
		}
		if(obj instanceof Person){
			Person person = (Person)obj;
			if(person.name.equals(this.name) && person.age == this.age){
				return true;
			}else{
				return false;
			}
		}
		return false;
	}
	//重写Object类的toString方法
	public String toString(){
		return "姓名:"+this.name+",年龄:"+this.age;
	}
}
public class Demo
{
	public static void main(String[] args){
		Person p1 = new Person("张三",22);
		Person p2 = new Person("张三",22);
		System.out.println(p1.equals(p2)? "是同一个人!":"不是同一个人!");
		System.out.println(p1.equals("哈哈")? "是同一个人!":"不是同一个人!");
	}
}


输出运行结果:

---------- java ----------
是同一个人!
不是同一个人!


在Object中equals方法默认是比较对象的地址,并不能对对象的内容进行比较。


Object可以接收任意引用类型的对象,并对其转换。

使用Object接收接口实例:

public class Demo
{
	public static void main(String[] args){
		A a = new B();
		Object obj = a ;
		A x = (A)obj;
		System.out.println(x.print());
	}
}
interface A 
{
	public String print();
}
class B implements A 
{
	public String print(){
		return "hello 土豪..";
	}
}


 

 


虽然接口不能继承一个类,但是依然是Object类的子类,因为接口本身是引用数据类型,所有可以进行向上转型操作。同样也可以使用Object接收一个数组,因为数组本身也是引用数据类型。

public class Demo
{
	public static void print(Object obj){
		if(obj instanceof int[]){
			int x[] = (int[])obj;
			for(int i = 0;i< x.length; i++){
				System.out.print(x[i]+"\t");
			}
			System.out.println();
		}
	}
	public static void main(String[] args){
		int[] temp = {1,3,5,7,9};
		Object obj = temp;
		print(obj);
	}
}


 

 

字符串(String)类

 

String类是immutable(不可改变)的Unicode字符序列,其作用是实现一种不能改变的静态字符串。

字符串变量存储一个对String对象的引用,它保存的是该String对象在内存中的位置,当我们声明和初始化变量时,它链接到字符串的初始值上。当执行赋值语句时,原始的链接将失去作用,而原始的字符串将被废弃掉,此时变量存储对新字符串的引用。这就意味着我们不能扩展String变量所引用的字符串,String对象被认为是不可改变的,即不能进行任何修改。

String创建字符串的方法:

1).String s = "Hello!";用字符串常量自动创建String实例(采用字符串字面值方式赋值)推荐使用此方式。

2).String s = new String("Hello!");通过String对象或字符串常量传递给构造方法

采用方式一、

public class Demo{
	public static void main(String[] args){
		String s = "Hello";//采用字面值方式赋值
		s += " world!";
		System.out.println(s);
	}
}

采用方式二、

 

public class Demo{
	public static void main(String[] args){
		String str = new String("Hello ");//采用对象实例化方式创建字符串
		str += "world!";
		System.out.println(s);
	}
}		

 

在程序中字符串的使用占了很大比例,Java语言提供了字符串池(String Pool),由String类维护。字符串池就是Java虚拟机内部的一个常量池,我们采用字符串字面值方式创建的String对象就是放置在字符串池中的。
分析String s = "Hello"的创建过程:(推荐使用此方法)

查找字符串池(String Pool)中是否存在"Hello"这个对象,如果不存在,则在字符串池(String Pool)创建一个"Hello"对象,然后将字符串池(String Pool)中的这个对象的地址返回来赋给引用变量s,这样s就会指向字符串池(String Pool)的这"Hello"

字符串对象。

如果在字符串池(String Pool)存在"Hello"对象,则不创建任何对象,直接将字符串池(String Pool)中的这个"Hello"对象地址返回赋给s引用。

分析String str = new String("abc")的创建过程:

首先在字符串池(String Pool)中查找有没有"abc"这个字符串对象,如果有,则不在字符串池(String Pool)中再去创建"abc"这个对象了,直接在堆(heap)中创建一个"abc"字符串对象,然后将堆(heap)中的这个"abc"对象的地址返回来赋给str引用,此时str指向了堆(heap)中创建的这个"abc"字符串对象。

如果字符串池(String Pool)中没有"abc"这个字符串对象,则在池中创建一个"abc"对象,然后再在堆(heap)中创建一个"abc"对象,将堆(heap)中的这个"abc"对象的地址返回并赋给str引用,此时str指向了堆(heap)中所创建的这个"abc"对象。

分析以下代码一共创建了几个对象?

String str1 = "abc";
String str2 = "abc";
String str3 = str1 + str2; 

 

首先是在字符串池中创建一个"abc"对象,返回赋给str1,而str2在创建之前先要在池中查找是否存在已有对象"abc",有则直接返回赋给str2,没有则创建。这里"abc"对象在池中已经存在了直接返回赋给str2即可,就不需要再创建对象"abc"了。str3是由str1和str2拼接而成,那么就会在池中新建一个对象,并且把创建好的对象地址返回来赋给str3引用。所以在上面的代码中一共只创建了2个对象

 

String s1 = new String("hello");
String s2 = new String("hello");

 

对于s1和s2,他们肯定会在堆(heap)中创建两个不同的"hello"对象,然后在字符串池(String Pool)中创建一个"hello"对象,如果有了就不再创建了,所以s1和s2完成初始化,一个创建了3个对象,在堆(heap)创建了2个,在字符串池(String Pool)中创建了1个。

现在搞清楚了String类的混淆点,接下来看看字符串的比较。

对于Object类中的equals()方法,Java中的每个类都具有该方法,对于此方法来说,它比较的是两个对象的引用是否指向同一个对象。此equals方法比较特殊,它可以等价与"=="。

public boolean equals(Object obj) {
	return (this == obj);
}

 

对于String类的equals()方法来说,它重写了Object类的equals方法,它是判断当前字符串与传进来的字符串的内容是否一致。

 public boolean equals(Object anObject) {
	if (this == anObject) {
	    return true;
	}
	if (anObject instanceof String) {
	    String anotherString = (String)anObject;
	    int n = count;
	    if (n == anotherString.count) {
		char v1[] = value;
		char v2[] = anotherString.value;
		int i = offset;
		int j = anotherString.offset;
		while (n-- != 0) {
		    if (v1[i++] != v2[j++])//字符比较
			return false;
		}
		return true;
	    }
	}
	return false;
}

 

我们想要比较两个String对象的相等性(比较两个字符串的内容是否相等),使用equals()方法,而不是用"=="(比较的是两个字符串的地址值)。

public class Demo{
	public static void main(String[] args){
		String str1 = "abc";
		String str2 = "abc";
		System.out.println(str1==str2); // true
		String s1 = new String("hello");
		String s2 = new String("hello");
		System.out.println(s1 == s2); //false
		System.out.println(s1.equals(s2));//true
	}
}


对于String类其他方法以及相关操作在后面Java常用类库再详细讲述。

 

总结:

1).抽象类与抽象方法

2).接口,接口的实现与接口的继承

3).多态续,接口类型的多态

4).static属性、static方法、static代码块

5).final变量、final方法、final类

6).单列设计模式

7).Object类,equals()方法

8).String类,equals方法,字符串池(String pool)

 

 

 

 

------- android培训java培训、期待与您交流! ----------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值