面向对象基础 中

面向对象基础 封装

封装

如何实现封装呢?

通俗的讲,封装就是把该隐藏的隐藏起来,该暴露的暴露出来。那么暴露的程度如何控制呢?就是依赖访问控制修饰符,也称为权限修饰符来控制。

访问控制修饰符来控制相应的可见边界,边界有如下:

(1)类

(2)包

(3)子类

(4)模块:Java9之后引入

权限修饰符

权限修饰符:public,protected,缺省,private

修饰符 本类 本包 其他包子类 其他包非子类
private × × ×
缺省 × ×
protected ×
public

外部类:public和缺省

成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private

提示:protected修饰非静态成员,**跨包时,**只能在子类的非静态成员中访问,在静态成员中无论是否创建对象都不能访问。

总结 :

 //静态直接访问非静态都不行
  /* System.out.println(a);
    System.out.println(b);
    System.out.println(c);
    System.out.println(d);*/
      

 public int a;
    protected int b;//跨包非子类不可见
    int c;//跨包不可见
    private int d;// 本类

    public static int e;
    protected static int f; //跨包非子类不可见
    static int g;//跨包不可见 
    private static int h;//跨类不可见

成员变量/属性私有化问题

**成员变量(field)私有化之后,提供标准的get/set方法,我们把这种成员变量也称为属性(property)。**或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。

1、成员变量封装的目的

  • 隐藏类的实现细节
  • 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
  • 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

2、实现步骤

使用 private 修饰成员变量

private 数据类型 变量名 ;

public class Chinese {
    
    
    private static String country;
    private String name;
  	private int age;
    private boolean marry;
}

提供 getXxx方法 / setXxx 方法,可以访问成员变量,代码如下:

public class Chinese {
private static String country;
private String name;
private int age;
private boolean marry;

public static void setCountry(String c){
    
    
    country = c;
}

public static String getCountry(){
    
    
    return country;
}

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 setMarry(boolean m){
    
    
    marry = m;
}

public boolean isMarry(){
    
    
    return marry;
}

}

3、如何解决局部变量与成员变量同名问题

当局部变量与类变量**(静态成员变量)**同名时,在类变量前面加“类名.";

当局部变量与实例变量**(非静态成员变量)**同名时,在实例变量前面加“this.”

public class Chinese {
    
    
  	private static String country;
    private String name;
  	private int age;
    
    public static void setCountry(String country){
    
    
        Chinese.country = country;
    }
    
    public static String getCountry(){
    
    
        return country;
    }

	public void setName(String name) {
    
    
		this.name = name;
    }

    public String getName() {
    
    
        return name;
	}

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public int getAge() {
    
    
        return age;
    }
}

构造器(Constructor)

我们发现我们new完对象时,所有成员变量都是默认值,如果我们需要赋别的值,需要挨个为它们再赋值,太麻烦了。我们能不能在new对象时,直接为当前对象的某个或所有成员变量直接赋值呢。

可以,Java给我们提供了构造器。

1、构造器的作用

在创建对象的时候为实例变量赋初始值。

注意:构造器只为实例变量初始化,不为静态类变量初始化

2、构造器的语法格式

构造器又称为构造方法,那是因为它长的很像方法。但是和方法还有有所区别的。

【修饰符】 构造器名(){
    
    
    // 实例初始化代码
}
【修饰符】 构造器名(参数列表){
    
    
	// 实例初始化代码
}

代码如下:

public class Student {
    
    
	private String name;
	private int age;
	// 无参构造
  	public Student() {
    
    } 
 	// 有参构造
  	public Student(String name,int age) {
    
    
		this.name = name;
    	this.age = age; 
	}
  	
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	public int getAge() {
    
    
		return age;
	}
	public void setAge(int age) {
    
    
		this.age = age;
	}
}

注意事项:

  1. 构造器名必须与它所在的类名必须相同。
  2. 它没有返回值,所以不需要返回值类型,甚至不需要void
  3. 如果你不提供构造器,系统会给出无参数构造器,并且该构造器的修饰符默认与类的修饰符相同
  4. 如果你提供了构造器,系统将不再提供无参数构造器,除非你自己定义。
  5. 构造器是可以重载的,既可以定义参数,也可以不定义参数。
  6. 构造器的修饰符只能是权限修饰符,不能被其他任何修饰

标准JavaBean

JavaBean 是 Java语言编写类的一种标准规范。符合JavaBean 的类,要求:

(1)类必须是具体的和公共的,

(2)并且具有无参数的构造方法,

(3)成员变量私有化,并提供用来操作成员变量的setget 方法。

public class ClassName{
    
    
  //成员变量
    
  //构造方法
  	//无参构造方法【必须】
  	//有参构造方法【建议】
  	
  //getXxx()
  //setXxx()
  //其他成员方法
}

继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。

其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类超类(superclass)或者基类

继承描述的是事物之间的所属关系,这种关系是:is-a 的关系。例如,猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。

继承的好处

  • 提高代码的复用性
  • 提高代码的扩展性
  • 类与类之间产生了关系,是学习多态的前提

继承的格式

通过 extends 关键字,可以声明一个子类继承另外一个父类,定义格式如下:

【修饰符】 class 父类 {
	...
}

【修饰符】 class 子类 extends 父类 {
	...
}


继承的特点一:成员变量

1、父类成员变量私有化(private)

  • 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
  • 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:
/*
 * 定义动物类Animal,做为父类
 */
class Animal {
    
    
    // 定义name属性
	private String name; 
    // 定义age属性
    public int age;
	// 定义动物的吃东西方法
	public void eat() {
    
    
		System.out.println(age + "岁的" + name + "在吃东西");
	}
}

/*
 * 定义猫类Cat 继承 动物类Animal
 */
class Cat extends Animal {
    
    
	// 定义一个猫抓老鼠的方法catchMouse
	public void catchMouse() {
    
    
		System.out.println("抓老鼠");
	}
}

/*
 * 定义测试类
 */
public class ExtendDemo01 {
    
    
	public static void main(String[] args) {
    
    
        // 创建一个猫类对象
		Cat cat = new Cat()// 为该猫类对象的name属性进行赋值
		//cat.name = "Tom";// 编译报错
      
      	// 为该猫类对象的age属性进行赋值
		cat.age = 2;
        
        // 调用该猫的catchMouse()方法
		cat.catchMouse();
		
      	// 调用该猫继承来的eat()方法
      	cat.eat();
	}
}

2、父子类成员变量重名

我们说父类的所有成员变量都会继承到子类中,那么如果子类出现与父类同名的成员变量会怎么样呢?

父类代码:

结论:

(1)当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;

(2)当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super."进行区别。

使用格式:

super.父类成员变量名

    public void test() {
    
    
        System.out.println("父类继承的i:" + super.i); //父类的变量 i
        System.out.println("子类的i:" +i); //子类的变量I
//		System.out.println(super.j);  父类私有 禁止访问
        System.out.println("父类继承的j:" +getJ());
        System.out.println("子类的j:" +j);
        System.out.println("父类继承的k:" +k);
        System.out.println("子类的m:" +m);
    }

说明:虽然我们可以区分父子类的重名成员变量,但是实际开发中,我们不建议这么干。

继承的特点二:成员方法

我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

1、方法重写

注意事项:

1.@Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留

2.必须保证父子类之间方法的名称相同,参数列表也相同。
3.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。

注意:如果返回值类型是基本数据类型和void,那么必须是相同

4.子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private

5.几种特殊的方法不能被重写

  • 静态方法不能被重写
  • 私有等在子类中不可见的方法不能被重写
  • final方法不能被重写

this和super关键字

super和this类似,它指向了当前对象自己的父类型特征(也就是继承过来的那些东西)。

super和this区别是:this可以看做一个引用变量,保存了改对象的地址,是当前对象整体,而super代表的是父类型特征,是子类局部的一些东西,这些继承过来的东西已经在子类里面了,你可以输出整体this,但不能输出父类型特征super。因为super指向的东西不是一个整体,没法打印输出。

System.out.println(this);  //输出this.toString()的值
System.out.println(super);  //编译报错,需要'.'

​ this()或this(实参列表)

  • 只能调用本类的其他构造器

  • 必须在构造器的首行

  • 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环

在子类的构造器首行,用于表示调用父类的哪个实例初始化方法

super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。

继承的特点三:构造方法

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

首先我们要回忆两个事情,构造方法的定义格式和作用。

  1. 构造方法的名字是与类名一致的。

    所以子类是无法继承父类构造方法的。

  2. 构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量

    所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super() ,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。代码如下:

class Fu {
    
    
  private int n;
  Fu(){
    
    
    System.out.println("Fu()");
  }
}
class Zi extends Fu {
    
    
  Zi(){
    
    
    // super(),调用父类构造方法
    super();
    System.out.println("Zi()");
  }  
}
public class ExtendsDemo07{
    
    
  public static void main (String args[]){
    
    
    Zi zi = new Zi();
  }
}
输出结果:
Fu()
Zi()

如果父类没有无参构造怎么办?

public class Person {
    
    
	private String name;
	private int age;
	public Person(String name, int age) {
    
    
		this.name = name;
		this.age = age;
	}
	//其他成员方法省略
}
public class Student extends Person{
    
    
	private int score;
}

此时子类代码报错。

解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。

public class Student extends Person{
private int score;

public Student(String name, int age) {
    
    
	super(name, age);
}
public Student(String name, int age, int score) {
    
    
	super(name, age);
	this.score = score;
}

//其他成员方法省略

}

结论:

子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。

  • super():表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
  • super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
  • super()和super(实参列表)都只能出现在子类构造器的首行
class A{
    
    

}
class B extends A{
    
    

}

class Test{
    
    
    public static void main(String[] args){
    
    
        B b = new B();
        //A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
        //但是因为都是默认的,没有打印语句,看不出来
    }
}
class A{
    
    
	A(){
    
    
		System.out.println("A类无参构造器");
	}
}
class B extends A{
    
    
	B(){
    
    
		System.out.println("B类无参构造器");
	}
}
class Test{
    
    
    public static void main(String[] args){
    
    
        B b = new B();
        //A类显示声明一个无参构造,
		//B类显示声明一个无参构造,        
		//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
        //可以看到会输出“A类无参构造器"和"B类无参构造器")
    }
}

继承的特点四:单继承限制

Java只支持单继承,不支持多继承。

//一个类只能有一个父类,不可以有多个父类。
class C extends A{
    
    } 	//ok
class C extends AB...	//error

Java支持多层继承(继承体系)。

class A{
    
    }
class B extends A{
    
    }
class C extends B{
    
    }

顶层父类是Object类。所有的类默认继承Object,作为父类。

  1. 子类和父类是一种相对的概念。

    例如:B类对于A来说是子类,但是对于C类来说是父类

  2. 一个父类可以同时拥有多个子类

成员变量初始化

类初始化

1、类初始化的目的:为类中的静态变量进行赋值。

2、实际上,类初始化的过程时在调用一个()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化()方法体中。
clinit are the static initialization blocks for the class, and static field initialization

(1)静态类成员变量的显式赋值语句

(2)静态代码块中的语句

3、整个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。

结论:

每一个类都有一个类初始化方法()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。一个类,只会初始化一次。

实例初始化

1、实例初始化的目的:为类中非静态成员变量赋值

2、实际上我们编写的代码在编译时,会自动处理代码,整理出一个()的类初始化方法,还会整理出一个或多个的(…)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。

init is the (or one of the) constructor(s) for the instance, and non-static field initialization.

实例初始化方法的方法体,由四部分构成:

(1)super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()

(2)非静态实例变量的显示赋值语句

(3)非静态代码块

(4)对应构造器中的代码

特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面

3、执行特点:

  • 创建对象时,才会执行
  • 每new一个对象,都会完成该对象的实例初始化
  • 调用哪个构造器,就是执行它对应的实例初始化方法
  • 创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)

多态

1、格式

父类类型 变量名 = 子类对象;

父类类型:指子类对象继承的父类类型,或者实现的父接口类型。

例如:

class Person{
    
    
	private String name;
	private int age;
	
    Person(String name, int age){
    
    
        this.name = name;
        this.age = age;
    }
    
	public void speak(){
    
    
		System.out.println(name + "说:我今年" + age);
	}
}
class Man extends Person{
    
    
    Man(String name, int age){
    
    
        super(name,age);
    }
}
class Woman extends Person{
    
    
    Woman(String name, int age){
    
    
        super(name,age);
    }
}
class Test{
    
    
	public static void main(String[] args){
    
    
		Person[] arr = new Person[2];
		arr[0] = new Man("张三",23);
		arr[1] = new Woman("如花",18);		
		for(int i=0; i<arr.length; i++){
    
    
			arr[i].speak();
		}
		System.out.println("------------------------");
		
		show(new Man("张三",23));
		show(new Woman("如花",18));
	}
	
	public static void show(Person p){
    
    
		p.speak();
	}
}

张三说:我今年23
如花说:我今年18

2,编译时类型与运行时类型不一致问题

  • 编译时,看“父类”,只能调用父类声明的方法,不能调用子类扩展的方法;
  • 运行时,看“子类”,一定是执行子类重写的方法体;

代码如下:

定义父类:

public class Animal {
    
      
    public void eat(){
    
    
        System.out.println("吃~~~");
    }
}  

定义子类:

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 class Test {
    
    
    public static void main(String[] args) {
    
    
        // 多态形式,创建对象
        Animal a1 = new Cat();  
        // 调用的是 Cat 的 eat
        a1.eat();    
        //a1.catchMouse();//错误,catchMouse()是子类扩展的方法,父类中没有
        /*
        多态引用,编译时,看“父类”,只能调用父类声明的方法;
        	    运行时,看“子类”,一定是执行子类重写的方法体;
        */

        // 多态形式,创建对象
        Animal a2 = new Dog(); 
        // 调用的是 Dog 的 eat
        a2.eat();               
    }  
}

多态的应用

1、多态应用在形参实参

父类类型作为方法形式参数,子类对象为实参。

代码如下:

public class Test01 {
    
    
	public static void main(String[] args) {
    
    
		showAnimalEat(new Dog()); //形参 Animal a,实参new Dog() 
								//实参给形参赋值   Animal a = new Dog()   多态引用
		showAnimalEat(new Cat());//形参 Animal a,实参new Cat() 
								//实参给形参赋值   Animal a = new Cat()   多态引用
	}
	
	/*
	 * 设计一个方法,可以查看所有动物的吃的行为
	 * 关注的是所有动物的共同特征:eat()
	 * 所以形参,设计为父类的类型
	 * 	此时不关注子类特有的方法
	 */
	public static void showAnimalEat (Animal a){
    
    
        a.eat();
//        a.catchMouse();//错误,因为a现在编译时类型是Animal,只能看到父类中有的方法
    }

}

2、多态应用在数组

数组元素类型声明为父类类型,实际存储的是子类对象

public class Test02 {
    
    
	public static void main(String[] args) {
    
    
		/*
		 * 声明一个数组,可以装各种动物的对象,看它们吃东西的样子
		 */
		Animal[] arr = new Animal[2]; //此时不是new Animal的对象,而是new Animal[]的数组对象
									//在堆中开辟了长度为5的数组空间,用来装Animal或它子类对象的地址
		arr[0] = new Cat();//多态引用   左边arr[0] 是Animal类型,右边是new Cat()
							//把Cat对象,赋值给Animal类型的变量
		arr[1] = new Dog();
		
		for (int i = 0; i < arr.length; i++) {
    
    
			arr[i].eat();
//			arr[i].catchMouse();错误,因为arr[i]现在编译时类型是Animal,只能看到父类中有的方法
		}
	}
}

3、多态应用在返回值

方法的返回值类型声明为父类的类型,实际返回值是子类对象

public class Test03 {
    
    
	public static void main(String[] args) {
    
    
		Animal c = buy("猫咪");
		System.out.println(c.getClass());
		c.eat();
	}
	/*
	 * 设计一个方法,可以购买各种动物的对象,此时不确定是那种具体的动物
	 * 
	 * 返回值类型是父类的对象
	 * 
	 * 多态体现在   返回值类型  Animal ,实际返回的对象是子类的new Cat(),或new Dog()
	 */
	public static Animal buy(String name){
    
    
        if("猫咪".equals(name)){
    
    
            return new Cat();
        }else if("小狗".equals(name)){
    
    
            return new Dog();
        }
        return null;
    }
}

向上转型与向下转型

首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。这个和基本数据类型的转换是不同的。

但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。

class Animal {
    
      
    void eat(){
    
    
        System.out.println("~~~"); 
    } 
}  

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 watchHouse() {
    
      
        System.out.println("看家");  
    }  
}

class Test{
    
    
    public static void main(String[] args){
    
    
        Cat a = new Cat();//a编译时类型是Cat
        Animal b = a;//b编译时类型是Animal
        Object c = a;//c编译时类型是Object
        
        //运行时类型
        System.out.println(a.getClass());
        System.out.println(b.getClass());
        System.out.println(c.getClass());
        //以上输出都一样,都是Cat类型
        
       	//a,b,c的编译时类型不同
    	//通过a能调用Cat中所有方法,包括从父类继承的,包括自己扩展的
    	//通过b只能调用Animal类及它的父类有的方法,不能调用Cat扩展的方法
    	//通过c只能调用Object类才有的方法
    }
}

为什么要类型转换呢?

因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间,就会出现类型转换的现象。

但是,使用父类变量接收了子类对象之后,我们就不能调用子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换。

  • 向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型

    • 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,不能调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,一定是安全的,而且也是自动完成的
  • 向下转型:当左边的变量的类型(子类)<右边对象/变量的类型(父类),我们就称为向下转型

    • 此时,编译时按照左边变量的类型处理,就可以调用子类特有的变量和方法了
    • 但是,运行时,仍然是对象本身的类型
    • 此时,不一定是安全的,需要使用(类型)进行强制类型转换
    • 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断

示例代码:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型  
        Animal a = new Cat();  
        a.eat(); 				// 调用的是 Cat 的 eat

        // 向下转型  
        Cat c = (Cat)a;       
        c.catchMouse(); 		// 调用的是 Cat 的 catchMouse
        
        // 向下转型  
        //Dog d = (Dog)a;     //这段代码可以通过编译,但是运行时,却报出了ClassCastException 
        //这是因为,明明创建了Cat类型对象,运行时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,		//不符合类型转换的定义。
        //d.watchHouse();        // 调用的是 Dog 的 watchHouse 
        
        Animal a2 = new Animal();
       // Dog d2 = (Dog)a2;//这段代码可以通过编译,但是运行时,却报出了ClassCastException 
       // d2.watchHouse(); // 调用的是 Dog 的 watchHouse
    }  
}

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。

变量名/对象 instanceof 数据类型 

所以,转换前,我们最好先做一个判断,代码如下:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
    
    
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
    
    
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}

那么,哪些instanceof判断会返回true呢?

对象/变量的编译时类型 与 instanceof后面数据类型是直系亲属关系才可以比较
对象/变量的运行时类型<= instanceof后面数据类型,才为true

示例代码:

/*

  • 1、instanceof 前面的对象与后面的类型有没有要求

  • instanceof 前面的对象的编译时类型,必须与 instanceof后面的类型有直系关系

  • 2、instanceof 什么时候返回true

  • instanceof 前面的对象的运行时类型,确实 <= instanceof后面的类型,直系关系

*/

 public class TestInstanceof {
    
    
	public static void main(String[] args) {
    
    
		Man m = new Man();
 //		System.out.println(m instanceof Woman);//错误  m的编译时类型是Man,它和Woman不是直系关系
	Person p1 = new Man();
    System.out.println(p1 instanceof Woman);
	//可以,p1的编译时类型是Person,它和Woman是直系关系
    //但是p1的运行时类型是Man,返回false
	
	Person p2 = new Woman();
    System.out.println(p2 instanceof Woman);
	//p2的编译时类型是Person,它和Woman是直系关系
    //p2的运行时类型是Woman,返回true
	
	Person p3 = new ChineseWoman();
    System.out.println(p2 instanceof Woman);
    //p3的编译时类型是Person,它和Woman是直系关系
	//但是p3的运行时类型是ChineseWoman, ChineseWoman<=Woman,所以返回true

 }
 }

final关键字

final:最终的,不可更改的,它的用法有:

1、修饰类

表示这个类不能被继承,没有子类

final class Eunuch{
    
    //太监类
	
}
class Son extends Eunuch{
    
    //错误
	
}

2、修饰方法

表示这个方法不能被子类重写

class Father{
    
    
	public final void method(){
    
    
		System.out.println("father");
	}
}
class Son extends Father{
    
    
	public void method(){
    
    //错误
		System.out.println("son");
	}
}

3、声明常量

final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。

如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)

public class Test{
    
    
    public static void main(String[] args){
    
    
    	final int MIN_SCORE = 0;
    	final int MAX_SCORE = 100;
    }
}
class Chinese{
    
    
	public static final String COUNTRY = "中华人民共和国";	
	private String name;
	public Chinese( String name) {
    
    
		super();
		this.name = name;
	}
	public Chinese() {
    
    
		super();
	}
	public String getName() {
    
    
		return name;
	}
	public void setName(String name) {
    
    
		this.name = name;
	}
	//final修饰的没有set方法
	public static String getCountry() {
    
    
		return COUNTRY;
	}
}

Object根父类

如何理解根父类

java.lang.Object是类层次结构的根类,即所有类的父类。每个类都使用 Object 作为超类。

  • Object类型的变量与除Object以外的任意引用数据类型的对象都多态引用

  • 所有对象(包括数组)都实现这个类的方法。

  • 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:

    public class MyClass /*extends Object*/ {
      	// ...
    }
    

Object类的API

API(Application Programming Interface),应用程序编程接口。Java API是一本程序员的字典 ,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看src源码

​ 根据JDK源代码及Object类的API文档,Object类当中包含的方法有11个。今天我们主要学习其中的5个:

toString()

public String toString()

①默认情况下,toString()返回的是“对象的运行时类型 @ 对象的hashCode值的十六进制形式"

②通常是建议重写,如果在eclipse中,可以用Alt +Shift + S–>Generate toString()

③如果我们直接System.out.println(对象),默认会自动调用这个对象的toString()

因为Java的引用数据类型的变量中存储的实际上时对象的内存地址,但是Java对程序员隐藏内存地址信息,所以不能直接将内存地址显示出来,所以当你打印对象时,JVM帮你调用了对象的toString()。

例如自定义的Person类:

public class Person {
    
      
    private String name;
    private int age;

    @Override
    public String toString() {
    
    
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // 省略构造器与Getter Setter
}

getClass()

public final Class<?> getClass():获取对象的运行时类型

因为Java有多态现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致,因此如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法

	public static void main(String[] args) {
    
    
		Object obj = new String();
		System.out.println(obj.getClass());//运行时类型
	}

finalize()

protected void finalize():用于最终清理内存的方法

public class TestFinalize {
    
    
	public static void main(String[] args) {
    
    
		for (int i = 0; i < 10; i++) {
    
    
			MyData my = new MyData();
		}
		
		System.gc();//通知垃圾回收器来回收垃圾
		
		try {
    
    
			Thread.sleep(2000);//等待2秒再结束main,为了看效果
		} catch (InterruptedException e) {
    
    
			e.printStackTrace();
		}
	}
}
class MyData{
    
    

	@Override
	protected void finalize() throws Throwable {
    
    
		System.out.println("轻轻的我走了...");
	}
	
}

面试题:对finalize()的理解?

  • 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。
  • 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码
  • 每一个对象的finalize方法只会被调用一次。
  • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存

hashCode()

public int hashCode():返回每个对象的hash值。

hashCode 的常规协定:

  • ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
  • ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。

主要用于后面当对象存储到哈希表等容器中时,为了提高存储和查询性能用的。

	public static void main(String[] args) {
    
    
		System.out.println("Aa".hashCode());//2112
		System.out.println("BB".hashCode());//2112
	}

equals()

public boolean equals(Object obj):用于判断当前对象this与指定对象obj是否“相等”

①默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值

②我们可以选择重写,重写有些要求:

A:如果重写equals,那么一定要一起重写hashCode()方法,因为规定:

​	a:如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;

​	b:如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;

​	c:如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false

B:如果重写equals,那么一定要遵循如下几个原则:

​	a:自反性:x.equals(x)返回true

​	b:传递性:x.equals(y)为true, y.equals(z)为true,然后x.equals(z)也应该为true

​	c:一致性:只要参与equals比较的属性值没有修改,那么无论何时调用结果应该一致

​	d:对称性:x.equals(y)与y.equals(x)结果应该一样

​	e:非空对象与null的equals一定是false
class User{
    
    
	private String host;
	private String username;
	private String password;
	public User(String host, String username, String password) {
    
    
		super();
		this.host = host;
		this.username = username;
		this.password = password;
	}
	public User() {
    
    
		super();
	}
	public String getHost() {
    
    
		return host;
	}
	public void setHost(String host) {
    
    
		this.host = host;
	}
	public String getUsername() {
    
    
		return username;
	}
	public void setUsername(String username) {
    
    
		this.username = username;
	}
	public String getPassword() {
    
    
		return password;
	}
	public void setPassword(String password) {
    
    
		this.password = password;
	}
	@Override
	public String toString() {
    
    
		return "User [host=" + host + ", username=" + username + ", password=" + password + "]";
	}
	@Override
	public int hashCode() {
    
    
		final int prime = 31;
		int result = 1;
		result = prime * result + ((host == null) ? 0 : host.hashCode());
		result = prime * result + ((password == null) ? 0 : password.hashCode());
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
    
    
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (host == null) {
    
    
			if (other.host != null)
				return false;
		} else if (!host.equals(other.host))
			return false;
		if (password == null) {
    
    
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (username == null) {
    
    
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}
	
}

猜你喜欢

转载自blog.csdn.net/weixin_45905210/article/details/121345220