【JavaSE基础】02-面向对象

原文写于 2016 年,个人学习笔记,闲来无事,搬运至此,希望于各位有用。主要内容是:面向对象的基础概念、三大特性、链式编程等。

当年真的好有毅力,一字一字敲,一图一图画。

面向对象(Object-Oriented Programming)

1、面向对象概念

面向对象是相对于面向过程而言的,面向对象和面相对过程都是对于代码的组织方法,编程思想

(1)、面向过程:强调的是功能行为。代表语言是:C语言。

示例:将大象装进冰箱。

  • 基于面向过程的编程思想:
    -1、打开冰箱
    -2、将大象装进去
    -3、关上冰箱

打开、进去、关闭都是功能行为,在代码中的直观体现就是函数或方法。这就是一种面向过程的以功能行为为主体的思想体现。

(2)、面向对象(Object-Oriented Programming):将功能封装进对象,强调的是具备了功能的对象。

示例:将大象装进冰箱。

  • 基于面向对象的编程思想
    -1、设别有哪些类
    -2、识别类中的属性和行为
    -3、识别类间关系

具体分析:

  • A:识别类
    大象类
    冰箱类
    测试的Demo类
  • B:识别类中属性和行为
    大象:进去
    冰箱:打开、关闭
    Demo:main方法(JVM入口)
  • C:类间关系
    Demo类中的 main方法中,新建大象和冰箱的对象,使用它们的功能。

P.S:从上述的分析过捏中,可以看到即使是面向对象,其类中的功能定义,也是基于面向过程的编程思想也就是说:面向对象是基于面向过程的编程思想,而不是面向过程的替代,两者相辅相成

2、面向对象特点

面向对象是一种符合人类习惯思维的编程思想,是一种能够将复杂性的问题简单化、将程序员从执行者变成指挥者(角色转换)的编程思想。

万事万物皆对象

为了提高代码的复用性,将2次以上重复的代码块封装成方法。–面向过程
为了将完成指定功能的代码/方法集中,提高代码的可用性、复用性,使用对象改进。-- 面向对象

完成需求时:

  • (1)、先去寻找具有该功能的类,若存在,就创建对象,调用方法
  • (2)、若不存在,那么就定义一个具有该功能的类,再创建对象,调用方法

从而简化开发,提高复用。

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

  • (1)、面向对象开发:其实就是不断的创建对象,使用对象,指挥对象做事情
  • (2)、面向对象设计:管理和维护对象之间的关系
  • (3)、面向对象特征:
    • 封装(encapsulation)
    • 继承(inheritance)
    • 多态(polymorphism)

4、类(与对象)

使用计算机语言其实就是不断的描述事物。
Java中描述事物通过类来实现,类是具体事物的抽象,是概念级的意义。
对象即是该类事物实实在在存在的个体。

类(class)是Java中最小的基本单元

在这里插入图片描述

可以理解成:类就是图纸,汽车就是堆内存中的对象。

定义

  • 类(class):类就是对现实社会中同一类事物的抽象,是一组相关的属性和行为的集合,是一个抽象的概念。
  • 对象(object):对象是现实社会中实实在在存在的事物,是该类事物的具体表现形式、具体存在的个体。

    现实社会中同一类事物通过**抽象(abstarct)定义为类(class)**
  • 事物的**属性** 抽象为类的**成员变量(member variable)**
  • 事物的行为抽象为类的**成员方法(member function)**

综上,类主要有成员变量和成员方法组成,后面还会具体包括其他的组成部分(譬如:构造函数)

定义类,实际上就是在定义类中的成员

5、类的内存图解

创建对象的格式:

  • 类名 对象名 = new 类名();

调用类中成员的格式:

  • 1、对象名.成员变量名;
  • 2、对象名.成员方法名();

三种主要的调用方式:单独调用、输出调用、赋值调用

示例1:一个对象的内存图解

/*	需求:实现一个手机类
	分析:
	  1、识别类:手机类(Phone)、测试类(PhoneDemo)
	  2、识别类中成员:
		手机类(Phone):
		  a、成员变量:
			生产商(brand)、颜色(color)、价格(price)
		  b、成员方法:
			打电话(call())、发短信(sengMessage())
			玩游戏(playGame())
		测试类(PhoneDemo):
		  a、成员变量:
		  b、成员方法:main方法(JVM入口)
	  3、类间关系:
		在测试类中创建使用手机类的对象
*/
class Phone
{
	String brand;
	String color;
	int price;
	// 方法 暂时没有给出具体的实现
	public void call(String name)
	{}

	public void sendMessage()
	{}

	public void playGame()
	{}
}

class PhoneDemo
{
	public static void main(String[] args)
	{
		// 创建类的对象
		Phone p = new Phone();
		System.out.println(p.brand+"\t"+p.color+"\t"+p.price);

		// 调用类的成员变量,并为其赋值
		p.brand = "Huawei";
		p.color = "Write";
		p.price = 1799;
		System.out.println(p.brand+"\t"+p.color+"\t"+p.price);
		
		p.call("Yzk");
		p.sendMessage();
		p.playGame();
	}
}

>>> null	null	0
	Huawei	Red		1799

内存图解:
在这里插入图片描述

示例2:两个对象的内存图解

/*
	使用示例1中创建的Phone类
	继续实现不同PhoneDemo
*/
class PhoneDemo
{
	public static void main(String[] args)
	{
		// 创建类的对象
		Phone p = new Phone();
		System.out.println(p.brand+"\t"+p.color+"\t"+p.price);

		// 调用类的成员变量,并为其赋值
		p.brand = "Huawei";
		p.color = "Write";
		p.price = 1799;
		System.out.println(p.brand+"\t"+p.color+"\t"+p.price);
		
		p.call("Yzk");
		p.sendMessage();
		p.playGame();
		
		// 创建对象p2指向p
		Phone p2 = p;
		System.out.println(p2.brand+"\t"+p2.color+"\t"+p2.price);

		p2.brand = "Apple";
		p2.color = "Golden";
		p2.price = 5899;

		System.out.println(p.brand+"\t"+p.color+"\t"+p.price);

		System.out.println(p2.brand+"\t"+p2.color+"\t"+p2.price);
	}
}

>>> null    null    0
    Huawei  Write   1799
    Huawei  Write   1799
    Apple   Golden  5899
    Apple   Golden  5899

内存图解:
在这里插入图片描述

6、成员变量和局部变量的区别

  • A:类中位置不同
    -局部变量;类中方法中或声明上
    -成员变量:类中成员位置,方法外

  • B:内存中位置不同
    -局部变量;栈中
    -成员变量:堆中

  • C:生命周期不同
    -局部变量;随着方法的调用而存在,方法的消失而消失
    -成员变量:随着对象的创建而存在,对象的消失而消失

  • D:初始化值不同
    -局部变量;没有初始化值,使用前必须自行初始化
    -成员变量:根据不同的数据类型,初始化值不同

堆内存中的默认初始化值:

  • 基本数据类型:
    byte、short、int、long:0
    float、double:0.0
    boolean:false
    char:‘\u0000’ = 空字符

  • 引用数据类型:
    类(class):null
    接口(interface):null
    数组(array[]):null

7、形式参数

  • 基本数据类型:形参的改变不影响实参
  • 引用数据类型:形参和实参同步变化

形参是引用类型的时候该如何调用呢

示例3:

class Student
{
	String name;
	int age;

	public Student(){}
	
	public Student(String sname, int sage)
	{
		name = sname;
		age = sage;
	}

	public void show()
	{
		System.out.println("Name : "+name+", Age : "+age);
	}
}

class StudentTools
{
	public void method(Student s)
	{
		s.show();
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Student s = new Student("Joian", 18);
		StudentTools st = new StudentTools();
		// 方法method需要的参数是Student类型
		st.method(s);
	}
}

>>> Name : Joian, Age : 18

引用类型作为参数的时候,传递的是具体的对象,而不是抽象的类

8、匿名对象

定义:没有指定对象名的对象,叫做匿名对象。譬如:new Student();

应用

  • 1、调用方法,且只调用一次
    new Student(“Joian”,18).show();
    **优点:**匿名对象使用完毕之后就是垃圾,可以及时被垃圾回收器回收, 提高内存利用率。尤其是在移动端的开发中,尤其多使用,因为移动端的内存很宝贵。
  • 2、匿名对象作为实际参数传递:
    st.method(new Student(“Joian”,18));

综合上述两个匿名对象的实际应用的例子改写示例3中的Demo类:

class Demo
{
	public static void main(String[] args)
	{
		// 这种方式也叫做链式编程(后面会涉及到)
		new StudentTools().method(new Student("Joian",18));
	}
}

>>> Name : Joian, Age : 18

9、三大特征之–封装(encapsulation)

继续之前的示例3,我们仅仅使用Student类,重定义Demo类

class Demo
{
	public static void main(String[] args)
	{
		Student s = new Student();
		s.name = "Yzk";
		s.age = -9;
		s.show();
	}
}

>>> Name : Yzk, Age : -9
>// 输出了预想的结果,代码具有正确性
>// 然而,年龄为负数,这明显不合理
>// 因此我们在赋值的时候,想要对年龄值进行校验

重定义Student类和Demo类

class Student
{
	String name;
	// 成员变量age私有化,防止不合法的赋值
	private int age;
	
	// 提供公共的访问方式,并做必要的合法性验证
	public void setAge(int age)
	{
		if(age >= 0)
		{
			// 为了防止局部变量age覆盖成员变量age,
			// 使用this关键字
			this.age = age;
		}
	}
	
	public void show()
	{
		System.out.println("Name : "+name+", Age : "+age);
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Student s = new Student();
		s.name = "Yzk";
		s.setAge(-9);
		// 合法值的验证自行练习
		s.show();
	}
}

>>> Name : Yzk, Age : 0
>// 使用的非法年龄值(小于0),将直接使用默认初始化值 0
>// 如果还要使用 对象名.成员变量名 进行赋值,会引发编译错误
>// 因为此时的age在类外是不可见的

P.S. 一般在set方法中,不会做任何的验证操作

定义:封装(encapsulation)是指隐藏对象的属性和实现细节,仅提供公共的访问方式。类、方法以及被private修饰的成员变量等都是封装的体现

好处

  • A:隐藏实现细节,提供公共的访问方式(私有属性)
  • B:提高代码的复用性(类、方法)
  • C:提高安全性(封装的目的)

将Student类中的属性私有化,并提供公共的访问方式,实现封装。

示例4:

class Student
{
	private String name;
	private int age;

	public Student(){}
	
	public Student(String sname, int sage)
	{
		name = sname;
		age = sage;
	}

	// name读写器
	public void setName(String name)
	{
		this.name = name;
	}
	
	public String getName()
	{
		return this.name;
	}

	//age读写器
	public void setAge(int age)
	{
		this.age = age;
	}
	
	public int getAge()
	{
		return this.age;
	}
	
	public void show()
	{
		System.out.println("Name : "+name+", Age : "+age);
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Student s = new Student();
		s.setName("Joian");
		s.setAge(18);
		// 上述三句话,等价于
		//	Student s = new Student("Joian",18);
		// 但是使用set进行设置值时,具有更好的灵活性
		
		System.out.println("Name: "+s.getName()+", Age:"+s.getAge());
	}
}

>>> Name: Joian, Age:18

原则

  • A:将不需要对外提供的内容封装起来
  • B:属性隐藏,提供公共的访问方式(读写器)

10、private关键字

概述:

A:是一个权限修饰符
B:可以用来修饰成员(成员变量、成员方法等)
C:被其修饰的成员,只可以在本类中访问

常见应用:

A:将成员变量用private修饰
B:提供公共的访问方式

// A:将成员变量用private修饰
private String name;

// B:提供公共的访问方式
public void setName(String name)
{
	this.name = name;
}

public String getName()
{
	return this.name;
}

11、this关键字

定义:当前类的对象引用

应用:解决局部变量隐藏成员变量的问题

// 成员变量
private String name;

public void setName(String name)
{
	// 使用哪个对象来调用setName方法。这里的this就是那个对象
	this.name = name;
}

理解:方法被哪个对象调用,this就指向那个对象

图解this关键字
在这里插入图片描述

12、构造方法

作用:给对象的数据(成员)初始化

格式

  • A:方法名和类名相同
  • B:没有返回值类型,连void都没有
  • C:没有返回值(没有return)

:任何void方法都可以用return; ,只是通常情况下,习惯省略

注意

  • A:若没有给出构方法。系统会自动提供一个无参构造
    publid 类名( ) { };譬如:public Student( ) { }
  • B:若已经给出了构造方法,系统将不再提供无参构造方法,需要自行给出。建议永远自己给出无参构造。

成员变量的赋值

  • A:私有属性 + 读写器(getXxx( )、setXxx( ):Xxx指的是对应属性首字母大写)
  • B:带参构造方法
	// A:私有属性 + 读写器(getXxx( )、setXxx( ))
	private String name;

	// name读写器
	public void setName(String name)
	{
		this.name = name;
	}
	
	public String getName()
	{
		return this.name;
	}

	// B:带参构造方法
	public Student(String sname, int sage)
	{
		name = sname;
		age = sage;
	}

类的组成:成员变量、成员方法、构造方法

成员方法分类

  • A:按照返回值:
    有明确返回值的方法(非void)
    返回void类型的方法(void)
  • A:按照参数:
    带参方法(带参方法)
    无参方法(空参方法)

基本类的标准代码写法
在这里插入图片描述

练习:示例4中的Student就是一个基本类的标准写法,请参考写出Phone的标准写法

13、类的初始化

class Student
{
	private String name = "Yzk";
	private int age = 18;
	
	public Student()
	{
		name = "Ian";
		age = 35;
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Student s = new Student();
	}
}

分析:

在这里插入图片描述在这里插入图片描述

ps:在编译生成class文件时,会自动产生两个方法,一个是类的初始化方法<clinit>, 另一个是实例的初始化方法<init>

  • <clinit>:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。
  • <init>:在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。

结合之前的所有的知识 完成以下的练习:

  • 1、定义Tools.class,求两个数字之和
    定义Demo.class,测试Tools的功能。
  • 2、定义长方形类,求周长和面积
    定义Demo类,测试功能。
  • 3、定义一个员工类(基本类的标准写法)
  • 4、定义MyMath.class,实现四则运算

思考一下:能否将进行四则运算的a、b定义为MyMath的成员变量呢?
答案是否定的,首先来看一下类的定义:描述同一类事物的属性和方法的集合,很显然Tools和MyMath只是一个工具类,不需要参与运算的a、b作为它们的属性。

练习总结

  • A:变量什么时候定义为成员变量? 当变量是用来描述类的时候。
  • B:变量到底定义在哪里? 变量的作用域越小越好,因为可以及时地被回收,提高内存利用率。

简单介绍一下import关键字,用于导包的关键字,使用时候,注意一定要在所有类的前面

14、static关键字

思考一下:练习4中,我们很明显可以感受到MyMath是一个工具类,因为它只是把数学上的四则运算封装在一个类中,方便使用而已。既然是一个工具类,就像99乘法表一样,每个人拿到的一样的,而不是说是存在各式各样的不同的版本,你的1×1=1,我的就变成1×1=2这样,也就是说这个**类中的方法都是被共享的,谁用谁直接拿去**。这就是static关键字的一个作用体现,先看一下修改后的MyMath.class吧。

class MyMath
{
	public static int add(int a, int b)
	{
		return a + b;
	}
	
	public static int subtract(int a, int b)
	{
		return a - b;
	}
	
	public static int multiply(int a, int b)
	{
		return a * b;
	}
	
	public static int divide(int a, int b)
	{
		if(b != 0)
		{
				return a / b;
		}
		// 只是返回 b 本身,没任何意义
		return b;
	}
}

class Demo
{
	public static void main(String[] args)
	{
		System.out.println(MyMath.add(12,23));
		System.out.println(MyMath.subtract(12,23));
		System.out.println(MyMath.multiply(12,23));
		System.out.println(MyMath.divide(12,23));
	}
}

对比代码,可以发现,只是在类中每个方法返回值类型之前加上**static**,并且在调用的时候,直接使用 类名.方法名()

示例:阅读代码以下片段,分析结果

// 请将代码修改成基本类的标准代码
class Person
{
	String name;
	int age;
	static String country;
	
	public Person()
	{}
	
	public Person(String name, int age)
	{
		this.name = name;
		this.age = age;
	}
	
	public void show()
	{
		System.out.println("Name:"+name+",Age:"+age+",Country: "+country);
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Person p1 = new Person("Yzk",18);
		p1.country = "China";
		p1.show();
		
		Person p2 = new Person("Joian",25);
		p2.show();
		p2.country = "USA";
		p1.show();
		p2.show();
	}
}
>>> Name:Yzk,Age:18,Country: China
    Name:Joian,Age:25,Country: China
    Name:Yzk,Age:18,Country: USA
    Name:Joian,Age:25,Country: USA

内存图解:
在这里插入图片描述
在这里插入图片描述

注意事项

  • A:静态方法中。没有this关键字
    Error:无法从静态上下文引用非静态变量
    静态成员是随着类的加载而存在的(先)
    this是随着对象的创建而存在的(后)

  • B:静态方法中的只能访问静态成员(成员变量和方法)
    这就是为什么在之前刚开始涉及到方法的时候,修饰符使用 public static 的原因,因为main方法是 public static

static 关键字可以修饰类中成员(成员变量、成员方法)


特点

  • A:static修饰的成员 随着类的加载而存在
  • B:优先于对象存在
  • C:被类的所有对象共享(Person类中的country)
  • D:可以直接通过类名调用,也可以通过对象名调用
    推荐使用类名调用,以区别于非静态成员
  • E:静态修饰的成员 称为 类成员(与类有关)
    非静态修饰的成员 称为 实例成员、对象成员
     
    PS:实例(instance)和对象(object)是相同的概念

静态方法中:
  只能访问静态的成员变量、成员方法

非静态方法中:
  可以访问静态或非静态的成员变量、成员方法

15、静态变量和成员变量的区别

  • A:所属不同
    静态变量属于类,称为 类变量
    成员变量属于对象,称为 对象变量或实例变量
  • B:内存位置不同
    静态变量在方法区中的静态区
    成员变量在堆内存中
  • C:内存中出现时间不同
    静态变量随着类的加载而加载,消失而消失
    成员变量随着对象的创建而存在,对象的消失而消失
  • D:调用不同
    静态变量可以使用类名和对象名调用,推荐使用前者
    成员变量只可以使用对象名调用

main方法详解 | 自学类的过程

*1、main方法详解
public static void main(String[] args) {...}

/*
 1、public 公共的,访问权限最大 ==> 权限足够大才可以供 JVM调用
 2、static 静态的,随着类加载而加载,通过类名访问 ==> 方便JVM调用
 3、void 方法返回值是返回给调用者的 ==> JVM调用,不需要返回值
 4、main 常见的方法入口
 5、String[] args 字符串数组(早期用于键盘录入)

	格式:java Demo hello world java 1 2 3 [0.0 false null...]
		通过数组的常见操作取值...

示例

class Demo
{
	public static void main(String[] args)
	{
		for(int i = 0; i < args.length; i++)
		{
			System.out.print(args[i] + "\t");
		}
	}
}

>>> c:\code>java Demo hello world java 0 0.1 false
    hello   world   java    0       0.1     false
*2、工具类帮助文档的制作

测试类的作用:创建其它类的对象,并调用其它类的功能。回顾之前一维数组的常见操作,当时都是定义在Demo.class,即测试类中。基于面向对象(Objecty-Oriented)编程思想,强调将描述同一类事物的属性和方法封装在一起,方便代码的调用和维护。很显然,关于数组的操作,不应该被定义在测试类中,重构之前的代码,使用工具类ArrayTools封装所有关于数组的操作。测试类仅仅负责实现测试工具类。
A:识别方法:
 1、遍历数组:printArray()
 2、获取数组最值:getMax()
 3、数组逆置:reverse()
 4、数组查表法:getValue()
 5、基本查找:getIndex()

B:封装方法
 (参照之前的MyMath工具类的定义,将所有方法使用static修饰)

class ArrayTools
{
	// 构造方法私有化,不可创建对象
	private ArrayTools()
	{}
	/* public 公共的 访问权限,才可以使用类名调用
			否则编译错误:指定方法发不可视
	   static 静态关键字修饰 (静态方法才可以访问和静态成员)
	*/
	public static void printArray()
	{}
	public static int getMax()
	{}
	public static void reverse()
	{}
	public static int getValue()
	{}
	public static int getIndex()
	{}
}
*3、说明书的制作过程

工具类 ==> 文档注释 ==> javadoc.exe解析文档注释
格式:javadoc -d 目录 -author -version ArrayTools.java

/**
*	这是一个数组操作的工具类
*	@author Joian.Sun
*	@version v1.0
*/
public class ArrayTools
{
	/**
	*	私有化构造方法,禁止为工具类创建对象
	*/
	private ArrayTools()
	{}
	
	/**
	*	遍历数组中元素,实现格式化打印,格式是:[1,2,3,4]
	*	@param int[] 待遍历的数组
	*/
	public static void printArray(int[] arr)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("[");
		for(int i = 0; i < arr.length; i++)
		{
			sb.append(arr[i]);
			if(i != arr.length - 1)
			{
				sb.append(",");
			}
		}
		sb.append("]");
		System.out.println(sb);
	}
	
	/**
	*	获取数组中最大值元素
	*	@param arr 待获最值的数组
	*	@return max 数组中的最大值
	*/
	public static int getMax(int[] arr)
	{
		int max = arr[0];
		for(int i = 1; i < arr.length; i++)
		{
			if(arr[i] > max)
			{
				max = arr[i];
			}
		}
		return max;
	}
	
	/**
	*	实现数组的就地逆置
	*	@param arr 待逆序的数组
	*/
	public static void reverse(int[] arr)
	{
		int start = 0;
		int end = arr.length - 1;
		while(start != end)
		{
			int t = arr[start];
			arr[start] = arr[end];
			arr[end] = t;
			
			start ++;
			end --;
		}
	}
	
	/**
	*	获取数组指定索引处的值,若索引越界,返回 index-length
	*	@param arr 待查找的数组
	*	@param index 指定的索引位置
	*	@return value 指定索引处的值
	*/
	public static int getValue(int[] arr, int index)
	{
		int value = index - arr.length;
		if(index >= 0 && index <= arr.length-1)
		{
			value = arr[index];
		}
		return value;
	}
	
	/**
	*	获取元素在数组中第一次出现的位置,若不存在,返回 -1
	*	@param arr 待查找的数组
	*	@param value 指定的元素值
	*	@return index 指定元素所在的索引
	*/
	public static int getIndex(int[] arr, int value)
	{
		// 若指定值在数组中不存在,返回 -1
		int index = -1;
		for(int i = 0; i < arr.length; i++)
		{
			if(arr[i] == value)
			{
				index = i;
			}
		}
		return index;
	}
}

常见编译错误

  • A:javadoc - 错误:找不到可以文档化的公共或受保护的类。
     javadoc文档编译的类需要使用public或protected修饰

  • B:javadoc - 警告:@param 后面跟上 形式参数名。数据类型不可以出现。

遇到编译错误,需要从上而下的排查,并每修改一个错误,就再次编译。因为Java自上而下的编译机制,可能后面数不清楚的错误只是因为最上面漏写的一个分号";"

*4、工具类和说明书的使用

在这里插入图片描述

*5、Java API

Oracle提供的API规范:Application Programming Interface(应用程序编程接口,帮助文档)

API的使用:

  • 1、显示 --> 索引 --> 录入关键字(Scanner)
  • 2、看包(java.lang包下的不需要导入,其它包下的需要手动导入)
    导包:import java.util.Scanner; (需要在所有的类上面导包)
  • 3、粗略浏览“解释和说明”部分,重点在于类的版本(从jdk几可以开始使用)、例子
  • 4、类的结构:
    成员变量 --对应于-- 字段摘要
    构造方法 --对应于-- 构造方法摘要
    成员方法 --对应于-- 方法摘要
  • 5、观察构造方法
    A:有构造方法:可以创建对象
    B:无构造方法:成员均是静态(使用类名访问)
  • 6、成员方法
    A:左边
    静态与否:决定如何(类名\对象名)调用
    返回值类型:决定使用什么类型变量接收
    A:右边
    方法名:准确书写
    参数列表:类型和个数要一致(不可以传入数据类型)
*6、通过API学习使用Math类

示例练习:猜数字小游戏(使用Math类实现 随机数生成)(自行完善注释)

import java.util.Scanner;

class Demo
{
	public static void main(String[] args)
	{
		int target = (int)(Math.random()*100)+1;

		while(true)
		{
			System.out.println("Please inout your number: ");
			int num = new Scanner(System.in).nextInt();
			
			if(num == target)
			{
				System.out.println("Equal");
				break;
			}
			else if(num < target)
			{
				System.out.println("Less");
				continue;
			}
			else
			{
				System.out.println("More");
				continue;
			}
		}
	}
}

16、代码块(code block)

Java中使用{ }括起来的代码称为代码块

(1)、根据位置和声明不同,分成以下几类:

  • A:局部代码块
    方法中,限定变量(局部变量)的生命周期,及早释放,提供内存的利用率。
  • B:构造代码块
    类中方法外,多个构造方法中相同的代码存放在一起,调用(new 新对象)就执行,并且在构造方法之前执行。
  • C:静态代码块
    类中方法外,使用static修饰,为了对类进行初始化,类加载就执行,并且只执行一次
  • D:同步代码块(多线程内容)

(2)、格式:

  • A:局部代码块:局部位置。
    作用:限定局部变量的生命周期
  • B:构造代码块:类中成员位置,用{ }括起来,每次调用构造方法之前,都会先执行。
    作用:对对象初始化,将多个构造方法中的共同的代码集中
  • C:静态代码块:类中成员位置,用{ }括起来,使用static修饰,随着类加载而执行,且只执行一次。
    作用:对类初始化

面试题

静态代码块、构造代码块、构造方法三者执行的顺序?
在这里插入图片描述

示例说明:

class Student
{
	private String name;
	private int age;
	
	{
		System.out.println("Student.构造代码块");
	}
	
	static
	{
		System.out.println("Student.静态代码快");
	}
	
	public Student()
	{
		System.out.println("Student.无参构造方法");
	}
	
	public Student(String name,int age)
	{
		System.out.println("Student.带参构造方法");
	}
}

class Demo
{
	static
	{
		System.out.println("Demo.静态代码快");
	}
	
	public static void main(String[] args)
	{
		System.out.println("Demo.main方法");
		Student s1 = new Student();
		Student s2 = new Student("Joian SUN", 18);
	}
}

>>> Demo.静态代码快
    Demo.main方法
    Student.静态代码快
    Student.构造代码块
    Student.无参构造方法
    Student.构造代码块
    Student.带参构造方法

说明:
在这里插入图片描述

注意:即使在Demo.class中存在着构造代码块和构造方法,也不会执行,因为没有创建对象,也不可能为测试类创建对象。

17、三大特征之–继承(inheritance)

Student.class和Teacher.class两个不同的类,可能会有什么是一样的呢?

  • 成员变量:姓名和年龄
  • 成员方法:打印个人信息的方法show

这样的重复,在定义类的时候,是需要书写两次的,如果此时再有教务员类,就需要再书写一次。在编程时候,这会造成代码量的重复增加。回想一下,方法的定义原则(当一个代码块,出现两次以上,就需要考虑定义方法),那么如何在类间消除代码的重复呢?这就引入了面向对象三大特征之一的继承(inheritance)。

定义:多个类中存在相同的属性和行为时,将这些内容抽取到一个独立类中,将现有类和独立类之间建立一个关系,使得现有类可以具有独立类的内容。这个关系就是继承(inheritance)。其中,抽取出来的独立类,叫做超类(super class),也称为父类或基类;而继承自它的现有类称为派生类或子类。子类在继承父类的基础上可以定义自己的新成员。

格式: class 子类 extends 父类

优点

  • A:提高代码的复用性
  • B:提高代码的可维护性
  • C:让类间产生关系,是多态(polymorphism)的前提
    缺点:类的耦合性紧密

PS:软件开发的原则:提高模块独立性(松散耦合、高度内聚)
   1、耦合性:模块之间的联系紧密程度的定性度量
   2、内聚性:模块内部的各成分之间的联系紧密程度

特点

  • A:Java语言中只支持单继承,不支持多继承
    C++中支持多继承:class Son extends Father, Monther。但是,在java中这个是不被允许的。Java中extends关键字之后只可以出现一个父类
  • B:Java语言支持多层(重)继承(构成了一个完整的继承体系)
// Java中多重继承体系
class Father extends GrandFather{}
class Son extends Father{}

注意事项

  • A:子类只可以继承父类所有得非私有成员(可以访问public、protected修饰的成员变量和成员方法)缺点:打破了封装性
  • B:子类不可以继承父类的构造方法,但可以使用super关键字访问父类的构造方法
  • C:不要为了部分功能而去继承(请看下例)
class A
{
	public void show1(){}
	public void show2(){}
}
class B
{
	public void show2(){}
	public void show3(){}
}

/*  不推荐使用继承的原因是,B不仅继承了需要的show2,还继承了
不需要的show1,这可能导致,B获得了不改拥有的权限,导致安全问题
class B extends A
{
	public void show3(){}
}
*/

继承使用条件:
  采用假设法:是否存在着 "IS a" 关系

比如说:使用Person抽象出来Student和Teacher中相同的属性和行为,是合理的。因为 Student is a Person ; Teacher is a Person

回顾一下,类的组成部分:成员变量、成员方法、构造方法。

在这里插入图片描述

示例1:继承中成员变量的访问顺序

// 本例毫无意义,只是为了演示成员变量的访问顺序问题
class A
{
	// (3)  父类  成员位置
	public int num = 10;
}

class B extends A
{
	// (2)  子类  成员位置
	public int num = 20;
	public void show()
	{
		// (1)  子类  局部位置
		int num = 30;
		// 从子类局部位置开始找
		System.out.println(num);
		// 从子类成员位置开始找
		System.out.println(this.num);
		// 从父类成员位置开始找
		System.out.println(super.num);
	}
}

class Demo
{
	public static void main(String[] args)
	{
		B b = new B();
		b.show();
	}
}

>>> 30
    20
    10
// 注释掉 (1)
>>> 20
    20
    10
// 注释掉 (1) (2)
>>> 10
    10
    10
// 注释掉 (1) (2) (3)
>>>错误: 找不到符号

this & super 关键字
在这里插入图片描述

在这里插入图片描述

示例2:继承中构造方法的访问问题

class Father
{
	private String name;
	public Father(String name)
	{
		this.name = name;
	}
}

class Son extends Father
{
	public Son() {}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

请根据提供的解决方案修改代码。使其可以通过编译。

在上面问题中,我们获取了一个简单的结论,那就是:类若存在父类,就需要先加载父类,再加载子类。 那么,一个类是如何初始化的呢?
 

明确以下几点

  • 已经学习过的成员变量的初始化顺序
    a、堆内存中的 默认初始化
    b、成员变量赋值中的 显示初始化
    c、构造方法初始化
  • 已经面试过的代码快执行顺序
    a、随着类加载而执行且只执行一次的 静态代码快
    b、每次创建对象都执行的 构造代码块
    c、创建对象时,JVM根据参数列表,选择执行的 构造方法
    d、随着成员方法的调用而执行的 局部代码快
  • 三个一句话总结
  • 静态内容随着类加载而加载
    子父类-分层初始化
  • 类在加载时,优先加载父类,再加载子类
  • 子类初始化(创建对象时)之前,优先进行父类的初始化。因为子类可能需要访问到父类数据

一个类的初始化过程

示例1:

class Father
{
	static
	{
		System.out.println("Father.静态代码快");
	}
	
	{
		System.out.println("Father.构造代码快");
	}
	
	public Father()
	{
		System.out.println("Father.构造方法");
	}
}

class Son extends Father
{
	static
	{
		System.out.println("Son.静态代码快");
	}
	
	{
		System.out.println("Son.构造代码快");
	}
	
	public Son()
	{
		System.out.println("Son.构造方法");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		Son s1 = new Son();
		Son s2 = new Son();
	}
}

>>> Father.静态代码快
    Son.静态代码快
    Father.构造代码快
    Father.构造方法
    Son.构造代码快
    Son.构造方法
    Father.构造代码快
    Father.构造方法
    Son.构造代码快
    Son.构造方法

说明:
在这里插入图片描述

示例2:

class X
{
	Y y = new Y();
	public X()
	{
		System.out.println("X");
	}
}

class Y
{
	public Y()
	{
		System.out.println("Y");
	}
}

class Z extends X
{
	Y y = new Y();
	public Z()
	{
		System.out.println("Z");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		new Z();
	}
}

>>> Y
    X
    Y
    Z

说明:虽然子类中每一个构造方法中默认第一条语句都是 super();但是初始化不是按照这个顺序进行的。super()仅仅表示的是分层初始化:亦即先对父类初始化,再对子类初始化。简单记:spuer([...]) 存在的意义,仅仅是JVM在进行父类初始化时,根据 super 参数列表调用对应的父类的构造方法初始化父类。

自行分析示例代码3:

class X
{
	Y y = new Y();
	public X(int num)
	{
		System.out.println("X");
	}
}

class Y
{
	public Y()
	{
		System.out.println("Y");
	}
}

class Z extends X
{
	Y y = new Y();
	public Z()
	{
		this(1);
		System.out.println("P");
	}
	
	public Z(double num)
	{
		this("ha");
		System.out.println("F");
	}
	
	public Z(int num)
	{
		this(2.1);
		System.out.println("Z");
	}
	
	public Z(String num)
	{
		super(1);
		System.out.println("G");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		new Z();
	}
}

>>> Y
    X
    Y
    G
    F
    Z
    P

说明:分层初始化时,从子类代码中直接找到 super 位置,确定父类调用哪个构造方法进行初始化,父类中找不到对应的构造方法,直接报错;子类代码中找不到 super 时,默认父类调用无参构造进行初始化,若父类没有无参构造,依然报错。


在这里插入图片描述

方法重写Override):子类中出现了和父类一模一样的方法声明(返回值类型也必须相同 ;方法名必须相同; 参数列表相同:参数个数相同和参数类型均相同;只是权限修饰符允许不同(只可以 >= 原来的权限),但最好保持一致),也称为方法覆盖、方法复写。稍有不同会导致:错误: 子类中的中的方法无法覆盖父类中的方法

应用:当子类需要父类的功能时,作为功能主体的子类想要拥有自己特有内容的时候,可以重写父类中的方法。这样既沿袭了父类功能,又有自己特有内容。

示例:新型手机在原来给别人打电话的同时可以听天气预报

class Phone
{
	public void call(String name)
	{
	System.out.println("Give " + name + " a call...");
	}
}

class NewPhone extends Phone
{
	public void call(String name)
	{
		super.call(name);
		System.out.println("Same time:Listening For The Weather!");
	}
}

class Demo
{
	public static void main(String[] args)
	{
		new NewPhone().call("Yzk");
	}
}

>>> Give Yzk a call...
    Same time:Listening For The Weather!

说明:super.call(name); 直接调用父类的call方法,表示说保留了原有功能,并在此语句之后定义了听天气预报的新功能
 

重写注意事项:

  • 父类的私有方法不可以重写(子类只能访问父类非私有成员
  • 子类重写父类方法时,访问权限不可以更低(最好保持一致
  • 父类中的静态方法,子类也必须通过静态方式重写
    (其实这个不是重写,暂时这么记忆,多态部分说明)

面试题

1、OverLoad 和 OverWrite 的区别? OverLoad 可以改变返回值类型吗? OverWrite 可以改变返回值类型吗?

  • 首先识别英文的含义:OverLoad(方法重载)、Override(方法重写)
  • OverLoad:同一类中,方法名相同、参数列表不同(参数个数或参数类型不同)的现象
  • OverWrite:子类中,出现和父类一模一样的方法声明的现象
  • OverLoad和返回值类型无关,可以修改
  • OverWrite和返回值类型有关,不可以修改;否则导致子类中的中的方法无法覆盖父类中的方法

2、this关键字和super关键字分别代表什么? 它们的使用场景和作用?

在这里插入图片描述

类加载时候,静态内容的执行顺序

class Demo
{
	public static void main(String[] args)
	{
		System.out.println("Demo.main方法");
	}

	static
	{
		System.out.println("Demo.静态代码快");
		show();
	}
	
	static
	{
		System.out.println("Demo.静态代码快");
		show();
	}
	
	static int num = 100;
	
	static
	{
		System.out.println("Demo.静态代码快");
		show();
	}
	
	static void show()
	{
		System.out.println(num);
	}
}

>>> Demo.静态代码快
    0
    Demo.静态代码快
    0
    Demo.静态代码快
    100
    Demo.main方法

// 先加载静态的内容,再执行静态的main方法
  • 自行更改 静态成员变量、静态成员方法和静态代码快的先后顺序,对比结果。可以得知,static成员按照 顺序执行,无论什么方法,不调用不执行,main方法是JVM调用的
  • 0\0\100 体现的是静态成员变量 num 所处的初始化的阶段不同,0\0的时候,是已进行默认初始化,100则是已经完成了 显示初始化
  • 关于类的加载和初始化,暂时作为初学者,按照上述理解,如果需要更加深入理解,请自行参考,Oracle官方提供的Java编程规范[The Java Language Specification]

18、final关键字

首先,我们先来回顾一下方法覆盖(Overriding):继承中,子类中有和父类一摸一样的方法声明的现象。但是,若我只是想要子类去继承就可以了,而不想要有任何的可能的修改,要怎么做呢?

因此,JAVA提供了一个关键字final去解决这一类的问题。

final的应用:

  • A:修饰类:无法从最终类继承,可用于修饰最底层类
  • B:修饰方法:无法重写最终方法(子类中的方法无法覆盖父类的同名方法)
  • C:修饰变量:无法为最终变量赋值(该变量实际上就是一个常量)

下面我们来回顾一下,在Java语言基础I中提到的常量:程序运行过程中,其值保持不变的量。并将常量分成了6个类别:整数常量、小数常量、布尔常量、字符常量、字符串常量、null常量。实际上这些都是**字面值常量。在java中还有一种常量叫做自定义常量**,这时就需要借助final来实现了。比如:final int num = 10; // num就是一个自定义的常量。

面试题

1、final修饰局部变量
 
根据局部变量的位置来讨论

  • A:方法声明上(形式参数):无法为最终变量赋值
    • 1、基本数据类型:值不可以改变
    • 2、引用数据类型:其地址值不可以改变
    • 注意:引用类型的地址值不可以改变,但是对象在堆内存中的值是可以改变的。
  • B:方法内部(局部变量):该值不可以改变

2、final修饰的变量的初始化时机?

  • A:final修饰的变量只可以初始化一次
  • B:给值时机:定义时显示初始化; 构造方法结束前初始化(且必须完成初始化,若在构造方法中还没有完成初始化,就会报错)。 违背了变量先初始化 再使用的规则
class X
{
	final int num ;
	public X(){}
}

class Demo
{
	public static void main(String[] args)
	{
		new X();
	}
}

>>> 错误: 可能尚未初始化变量num
      public X(){ }
                  ^
// 指到构造方法的结束花括号 "}",表示在构造方法结束之前必须为final变量完成一次显示赋值

19、三大特征之–多态(polymorphism)

定义:某事物在不同时刻表现出来的不同的状态

前提条件

  • A:继承关系 / 实现关系;
  • B:方法重写(可以没有,若没有的话,多态没有任何的意义)
  • C:父类或父接口指向子类(实现类)对象

示例:

class X
{
	public int num = 10;
	
	public X()
	{
		System.out.println("X.Constructor");
	}
	
	public void show()
	{
		System.out.println("X.MemberFunction");
	}
	
	static void method()
	{
		System.out.println("X.staticMethod");
	}
}

// 前提1、继承关系
class Y extends X
{
	public int num = 20;
	
	public Y()
	{
		System.out.println("Y.Constructor");
	}
	
	// 前提2、方法重写(OverWrite)、方法覆盖(Overriding)
	public void show()
	{
		System.out.println("Y.MemberFunction");
	}
	
	static void method()
	{
		System.out.println("Y.staticMethod");
	}
}
class Demo
{
	public static void main(String[] args)
	{
		// 前提3、父类引用指向子类对象
		// 构造方法
		X x = new Y();
		// 成员变量
		System.out.println(x.num);
		// 成员方法
		x.show();
		// 静态方法
		x.method();
	}
}

>>> X.Constructor
    Y.Constructor   // 构造方法
    10				// 成员变量
    Y.MemberFunction  // 成员方法
    X.staticMethod    // 静态方法

说明:多态中成员的访问特点:

  • A:成员变量:编译看左边,运行看左边
  • B:构造方法:创建子类对象时,优先初始化父类super()
  • C:成员方法:编译看左边,运行看右边
  • D:静态方法:编译看左边,运行看左边
     

PS: 静态和类相关(运行看左边);重写和对象有关(运行看右边)

根据多态实现的前提1的不同可以将多态简单分成:几乎不存在的具体类多态(上述示例)、常见的抽象类多态、最常见的接口多态

优点

  • 提高了代码的复用性、可维护性(多态前提:继承和方法重写来支持)
  • 提高了代码的扩展性(具体类多态虽然建议不要使用,所谓的猫狗案例,但是下面的这个猫狗案例绝对可以震惊一下的。什么载体不重要,重要的想要体现的思想原理,是否真的可以淋漓尽致

声明1:为了更好地专注于多态,成员变量我们暂不定义。
声明2:很多博主认为猫狗案例没有任何价值,BUT 我就写,因为我觉得这是让我更好理解特性的案例,爱咋咋地。
声明3:不仅写了,我还写个 6、7、8、9 版。

猫狗案例 version 1.0

/*
	需求:定义猫类、狗类
			成员变量:暂时不定义
			成员方法:eat()、sleep()
			构造方法:手动给出无参构造
		定义测试类Demo,测试猫狗类的功能
	
	分析编码
*/
class Cat
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		Cat c = new Cat();
		c.eat();
		c.sleep();
		
		Dog d = new Dog();
		d.eat();
		d.sleep();
	}
}

>>> Cat.eat
    Cat.sleep
    Dog.eat
    Dog.sleep

审视示例代码,我们发现Cat和Dog类具有基本完全相同的行为,于是,我们想到去使用继承来将这些相同的东西去抽取出来,创建一个独立的类Animal。但是想一下,现实社会中存在着这个Animal吗?事实上,是不存在一个叫做Animal的事物的。在Java中,这种在现实社会中不存在事物与之对应的,我们创建类时,需要使用abstract修饰,创建一个抽象类。(先使用,后面会详细学习到)

猫狗案例 version 2.0(引入继承改进)

/*
说明:使用继承改进,将相同的内容臭渠道抽象类Animal中
抽象类格式:abstract class Animal  (先知道具体的格式就可以)

需求: 实现继承的基础上,多创建几个对象进行测试
*/
abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		Cat c1 = new Cat();
		c1.eat();
		c1.sleep();
		Cat c2 = new Cat();
		c2.eat();
		c2.sleep();
		Cat c3 = new Cat();
		c3.eat();
		c3.sleep();
		
		Dog d1 = new Dog();
		d1.eat();
		d1.sleep();
		Dog d2 = new Dog();
		d2.eat();
		d2.sleep();
		Dog d3 = new Dog();
		d3.eat();
		d3.sleep();
	}
}

>>> Cat.eat
    Cat.sleep
    Cat.eat
    Cat.sleep
    Cat.eat
    Cat.sleep
    Dog.eat
    Dog.sleep
    Dog.eat
    Dog.sleep
    Dog.eat
    Dog.sleep

完成继承之后,多创建了几个对象测试类的功能。但是问题也随着新的需求暴露出来了,测试类中eat、sleep方法一起调用的次数越来越多,对于这种重复代码块出现超过两次以上的现象,我们说过,需要使用方法来改进,以减少代码量,实现重复使用。但是问题来了,这个方法我们在哪里创建呢?首先,测试类中肯定不合理;其次,定义在实体类中,似乎可以,但是请回想一下:类的定义(用于描述现实社会中一类事物,一组与该事物相关的属性和行为的集合),边吃边睡,似乎不合实际。于是我们只能借助一个叫工具类(AnimalTool)的东西去将这两个方法了抽取到一起了。

值得提醒的是:工具类一般是不可以创建实例的。你懂了吗?

猫狗案例 version 3.0(工具类封装重复代码块)

/*
	需求: 使用工具类(AnimalTool)将重复代码块封装
*/
abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class AnimalTool
{
	// 私有化构造方法,不可类外创建对象
	private AnimalTool() { }
	
	// 使用 useCat、useDog分别封装Cat类与Dog类的eat和sleep方法
	public static void useCat(Cat c)
	{
		c.eat();
		c.sleep();
	}
	public static void useDog(Dog d)
	{
		d.eat();
		d.sleep();
	}
}
class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		Cat c1 = new Cat();
		AnimalTool.useCat(c1);
		Cat c2 = new Cat();
		AnimalTool.useCat(c2);
		Cat c3 = new Cat();
		AnimalTool.useCat(c3);
		
		Dog d1 = new Dog();
		AnimalTool.useDog(d1);
		Dog d2 = new Dog();
		AnimalTool.useDog(d2);
		Dog d3 = new Dog();
		AnimalTool.useDog(d3);
	}
}

>>> Cat.eat
    Cat.sleep
    Cat.eat
    Cat.sleep
    Cat.eat
    Cat.sleep
    Dog.eat
    Dog.sleep
    Dog.eat
    Dog.sleep
    Dog.eat
    Dog.sleep

修改后的代码,编译执行,发现和之前的结果一模一样,说明我们完成了需求。但是并不出色,此时,如果我们需要创建Pig类、Wolf类、Tiger类,并实现eat、sleep功能,怎么办呢?再者,如果我们还需要在测试类中多创建几个实例去测试类的功能,有如何处理?好像是都可以轻松实现的,但是为什么并不出色呢?继续往下看吧。

猫狗案例 version 4.0(创建新类,实现功能测试)

/*
	需求:创建三个新的类,实现类似Cat\Dog的功能主体,并多次测试
*/
abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class AnimalTool
{
	// 私有化构造方法,不可类外创建对象
	private AnimalTool() { }
	
	// 使用 useCat、useDog分别封装Cat类与Dog类的eat和sleep方法
	public static void useCat(Cat c)
	{
		c.eat();
		c.sleep();
	}
	public static void useDog(Dog d)
	{
		d.eat();
		d.sleep();
	}
	
	// 模仿 useCat、useDog实现新需求
	public static void usePig(Pig p)
	{
		p.eat();
		p.sleep();
	}
	public static void useWolf(Wolf w)
	{
		w.eat();
		w.sleep();
	}
	public static void useTiger(Tiger t)
	{
		t.eat();
		t.sleep();
	}
}
class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Pig extends Animal
{
	public Pig() { }
	
	public void eat()
	{
		System.out.println("Pig.eat");
	}
	public void sleep()
	{
		System.out.println("Pig.sleep");
	}
}

class Wolf extends Animal
{
	public Wolf() { }
	
	public void eat()
	{
		System.out.println("Wolf.eat");
	}
	public void sleep()
	{
		System.out.println("Wolf.sleep");
	}
}

class Tiger extends Animal
{
	public Tiger() { }
	
	public void eat()
	{
		System.out.println("Tiger.eat");
	}
	public void sleep()
	{
		System.out.println("Tiger.sleep");
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		/*
		Cat c1 = new Cat();
		AnimalTool.useCat(c1);
		Cat c2 = new Cat();
		AnimalTool.useCat(c2);
		Cat c3 = new Cat();
		AnimalTool.useCat(c3);
		
		Dog d1 = new Dog();
		AnimalTool.useDog(d1);
		Dog d2 = new Dog();
		AnimalTool.useDog(d2);
		Dog d3 = new Dog();
		AnimalTool.useDog(d3);
		*/
		// 模仿 实现新类的功能测试
		Pig p1 = new Pig();
		AnimalTool.usePig(p1);
		
		Wolf w1 = new Wolf();
		AnimalTool.useWolf(w1);
		
		Tiger t1 = new Tiger();
		AnimalTool.useTiger(t1);
	}
}

>>> Pig.eat
    Pig.sleep
    Wolf.eat
    Wolf.sleep
    Tiger.eat
    Tiger.sleep

解决了新的功能需求,我们来看一下,什么叫做并不出色。工具类应该是针对一类问题的,而且不应该是说,你每多点需求,就要修改工具类,这个是很不安全的。回想一下在API的时候,我们学习使用的Math类,我们需要的数学功能在Math类中,有需求直接调用(方法重载实现的)。我们再来看看,我们的工具类AnimalTool,显而易见,并不出色。下面我们来看一下,如何出色

每一个继承自抽象类Animal的类都需要AnimalTool工具类提供use方法,根据不同对象调用不同的方法。可以理解成:Animal在不同时刻表现出不同的状态:时而Cat、时而Pig、时而Tiger。而且这些类都继承并重写了Animal中的eat、sleep方法。继承、方法重写,不同时刻表现出不同的状态,这就是多态。似乎还差点意思:父类/父接口引用指向子类对象。如何合理给出最后一个前提呢?常规的,我们会能想到直接在测试类中定义:Animal d = new Dog(); 然后使用 AnimalTool.useDog(d);实现,虽然使用了多态,但是没有任何意义,编译前还是需要自己去确认调用谁的什么方法;增加需求之后呢,难免修改AnimalTool类。那么如何既可以使得调用哪个方法,在运行阶段由系统动态指定,又可以避免修改工具类AnimalTool呢?

猫狗案例 version 5.0(多态Polymorphism)改进工具类

abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class AnimalTool
{
	// 私有化构造方法,不可类外创建对象
	private AnimalTool() { }
	
	// 使用 useAnimal
	public static void useAnimal(Animal a)
	{
		a.eat();
		a.sleep();
	}
}
class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Pig extends Animal
{
	public Pig() { }
	
	public void eat()
	{
		System.out.println("Pig.eat");
	}
	public void sleep()
	{
		System.out.println("Pig.sleep");
	}
}

class Wolf extends Animal
{
	public Wolf() { }
	
	public void eat()
	{
		System.out.println("Wolf.eat");
	}
	public void sleep()
	{
		System.out.println("Wolf.sleep");
	}
}

class Tiger extends Animal
{
	public Tiger() { }
	
	public void eat()
	{
		System.out.println("Tiger.eat");
	}
	public void sleep()
	{
		System.out.println("Tiger.sleep");
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		Animal c1 = new Cat();
		AnimalTool.useAnimal(c1);
		
		Animal d1 = new Dog();
		AnimalTool.useAnimal(d1);

		Animal p1 = new Pig();
		AnimalTool.useAnimal(p1);
		
		Animal w1 = new Wolf();
		AnimalTool.useAnimal(w1);
		
		Animal t1 = new Tiger();
		AnimalTool.useAnimal(t1);
	}
}

>>> Cat.eat
    Cat.sleep
    Dog.eat
    Dog.sleep
    Pig.eat
    Pig.sleep
    Wolf.eat
    Wolf.sleep
    Tiger.eat
    Tiger.sleep

对比之前的实现,明显最终的AnimalTool的代码量减少了很多,并且避免了之后新需求出现,对AnimalTool类的频繁修改,保证了安全性和正确性,同时多态也保证了可扩展性。此时,如果需要创建一个新的类Iron,实现Animal的全部功能,并测试。实现:只要添加一个类Iron继承自Animal,并且在测试类中创建对象 ir,然后就可以直接使用AnimalTool.useAnimal(ir);实现功能了,不需要修改任何AnimalTool代码,这就是可扩展性。但是,到底是如何实现多态的第三个前提的呢?父类/父接口引用指向子类对象

图解说明
在这里插入图片描述

别具一格的猫狗案例,应用到面向对象的三大特征:继承(inheritance)、封装(encapsulation)、多态(polymorphism),以及抽象类。实际上就是一个抽象类多态的示例。但是,在类中,一些特有的方法是没有给出来的,比如说:Tiger特有一个狩猎功能(暂时不考虑是否和eat功能重复)hunting。

提示:给出方法定义,实现狩猎其他动物。为了突出问题,去掉了部分类的实现

猫狗案例 version 6.0(多态Polymorphism)实现狩猎方法

abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class AnimalTool
{
	private AnimalTool() { }
	
	public static void useAnimal(Animal a)
	{
		a.eat();
		a.sleep();
	}
}

class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class Tiger extends Animal
{
	public Tiger() { }
	
	public void eat()
	{
		System.out.println("Tiger.eat");
	}
	public void sleep()
	{
		System.out.println("Tiger.sleep");
	}
	
	public void hunting(Animal a)
	{
		// 反射和链式编程,暂不需要了解
		System.out.println(this.getClass().getName()+" Hunting "+a.getClass().getName());
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		Animal t1 = new Tiger();
		AnimalTool.useAnimal(t1);
		
		t1.hunting(new Dog());
	}
}

在Tiger类中实现了hunting方法,但是结果莫名其妙。 明明定义了hunting,但是结果却显示找不到符号:方法 hunting(Dog);错误原因:t1是Animal类型变量;编译看左边,Animal类中确实是没有hunting方法的,所以才会提示错误 那么如何解决呢?

解决方案

  • A:创建子类对象,调用方法即可以。(基本不使用
  Tiger t1 = new Tiger();
  t1.hunting(new Dog());
  • B:由多态的前提:父类/父接口引用指向子类对象。把父类引用强制转换为子类引用(向下转型 downcasting)
  Tiger t = (Tiger)(t1);
  t.hunting(new Dog());

对象之间的转型问题:

  • A:向上转型(upcasting):多态中,使用父类/父接口引用去指向子类对象的现象。譬如:Animal t1 = new Tiger();

向上转型(upcasting)并非是将子类自动向上转型为父类对象,相反它是从另一种角度去理解向上两字的:它是对父类对象的方法的扩充,即父类对象可访问子类从父类中继承来的和子类复写父类的方法。其它的方法都不能访问,包括父类中的私有成员方法、子类中特有的方法。

  • B:向下转型(downcasting):多态中,把父类/父接口引用强制转换为子类引用的现象。譬如:Tiger t = (Tiger)(t1);

向下转型(downcasting)则是为了**可以访问子类中特有的方法**,将父类对象强制转换成子类引用,它也是对父类对象的方法的扩充,即转换后可访问子类特有的方法。


多态&转型–内存图解

根据之前的类的定义,给出以下测试类:分析代码问题并总结!

class Demo
{
	public static void main(String[] arg)
	{
		Animal c = new Cat();
		AnimalTool.useAnimal(c);
		
		Animal t1 = new Tiger();
		t1.hunting(c);
		Tiger t = (Tiger)(t1);
		t.hunting(c);
		
		Dog d = (Dog)(t1);
	}

说明:
在这里插入图片描述

注意:多态的前提:父类引用指向子类对象。向下转型时必须要确认:子类 "is a" 父类的继承关系。

20、抽象类(abstract class)

abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

上面是之前我们为了实现多态,而抽取出来的抽象类Animal。现在我们详细来解释一下。

定义:抽象类往往是用来表征对问题域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。在Java中,一个没有方法体的方法,应该被定义成抽象方法。而包含抽象方法的类就是抽象类(abstract class)。

特点:

  • A :抽象类和抽象方法需要用抽象(abstract)关键字来修饰

抽象类不一定有抽象方法,包含抽象方法的类,一定要是抽象类。
注意区别:没有方法体(void eat();)和空方法体(void eat(){}

  • B:抽象类不可以实例化。

抽象不可以实例化,但有构造方法,用于子类访问父类的初始化。

  • C:抽象类的子类
  • 子类是抽象类(使用abstract修饰)。不用重写父类所有的抽象方法,可以留给子类的子类去实现。可以重写部分的抽象方法,但是依然不可实例化。
  • 子类是具体类(不使用abstract修饰)。必须重写父类所有的抽象方法,非抽象方法可以继承也可以重写。

抽象类的实例化是依靠具体类来实现的。是多态的形式:Animal a = new Cat();

抽象类成员特点

  • A:成员变量

可以是变量,也可以是自定义常量(final修饰)

  • B:构造方法

无参、带参均可。用于子类访问父类的初始化
super(); super(...);

  • C:成员方法
  • 抽象方法:强制子类必须做的(不一样的事情),子类具体类必须实现所有抽象方法。子类若是抽象类,则是子类的子类实现…
  • 非抽象方法:子类继承的方法(一样的事情),提高代码的复用性

面试题

1、一个类若无抽象方法,是否可以定义为抽象类?有什么意义?

可以定义为抽象类,意义在于不让其实例化。其中的方法主要用来给子类去继承或者重写调用,提高代码的复用性。


2、abstract不可以和哪些关键字共存?

  • A:private关键字 原因:冲突。抽象的主要目的是延迟到子类中实现,使用private之后子类无法访问。
  • B:final关键字 原因:同上。
  • C:static关键字 原因:无意义。static关键字主要是通过类名调用的,abstract修饰的方法是没有方法体的,因此毫无意义。

21、引用数据类型之三:接口(interface)

狗自古以来是人类最好的朋友。宠物狗更是作为家庭的一员一起陪着我们,为了开发他们的智力,宠物之家的工作人员总是会给我们很多的惊喜,它们学会了算术、学会帮我们分类生活垃圾。那么问题来了,这些后天的能力该定义在什么地方呢?

首先,我们可能想到建立在Dog类中,毕竟是Dog是我们最好的朋友。但是,难道Cat、Tiger就不可驯化吗?于是我们又把这些功能定义在了Animal类中,发挥继承的作用。然而,问题往往经不起深究,这些东西是用来描述Animal类的吗?所有动物都会被驯化吗?(你让野猪怎么想?不被驯化,就不是动物了吗?)额···,不是!遇到了人生瓶颈期了,有木有?回想一下,似乎我们还没有竭尽全力。我们想到把这些新的东西放到最上层得抽象父类Animal中、尝试过把它们定义在具体的子类Cat、Dog、Tiger中,为什么不创建新的东西呢?毕竟创新才是出路。重点是如何创新?先来学习一些新的知识。永远记住一个编程的真理:TO LEARN A LITTLE KNOWLEDGE, WRITE A LINE OF CODE ...

定义:为了体现事物功能的扩展性,Java中定义了接口去实现这些额外功能,并不给出具体的实现。

特点

  • A:接口用关键字interface表示

格式:interface 接口名 { }

  • B:类实现接口使用关键字implements

格式:class 类名 implements 接口名 { }

  • C:接口不可实例化
  • D:接口的子类(实现类)

抽象类(无意义)
具体类:必须重写父接口中所有抽象方法

接口成员特点

  • A:成员变量

只可以是静态自定义常量(默认是public static final,建议自己给出)

  • B:构造方法:

无构造方法! 那么子类如何实现父类的初始化呢?

  • Java中的所有类都默认继承自java.lang.Object类,该类只有无参构造(所有子类中构造方法默认第一条语句是 super();的缘由所在),没有成员变量。
  • 类Object是类层次结构中的根类,每个类都是用它作为超类。
  • C:成员方法:

接口中成员方法,不可以有方法体,只可以是抽象方法(默认修饰符是public abstract,建议自己给出)。

面试题

1、 类与类、类与接口、接口与接口之间的关系?

案例:遗传学,Son遗传来自于Father、Mother;社会学,Son的本质是Person;理解成Son在为Person的基础上实现了Father、Mother

  • A:类与类:继承关系,单继承,不可以多继承,但可以存在多层继承(多重继承)
    class Son extends Person{}
  • B:类与接口:实现关系,单实现、多实现均可
    class Son extends Person implements Father,Mother{}
  • C:接口与接口:可以单继承,也可以多继承
    implements Son extends Father,Mother{}

总结:Java中,可以单继承,也可以多继承;单实现多实现也均可。但是类之间只可以单继承,多重继承。


2、抽象类与接口的区别?

  • A: 成员区别:
  • 成员变量
    抽象类:变量、自定义常量均可
    接口:只可以是静态自定义常量(默认修饰符:public static final
  • 构造方法
    抽象类:无参构造、带参构造均可(不可实例化,供子类初始化父类数据)
    接口:无 构造方法(实现类默认继承自超类Object)
  • 成员方法
    抽象类:抽象方法(供具体子类实现、抽象子类继承)、非抽象方法(供子类继承)
    接口:只可以是抽象方法(默认修饰符:public abstract
     
  • B: 关系区别:
  • Java中可以单继承、也可以多继承,单实现、多实现也都可以。只是类之间的继承只可以是单继承,但是可以通过多重继承实现继承体系。
     
  • C: 设计理念区别:
  • 抽象类被继承体现地是“Is a”的关系;接口被实现体现地是“Like a”的关系
  • 抽象类中定义的是该继承体系的 共性功能;接口中定义的是该继承体系的 扩展功能

新的姿势(知识)get
现在来解决问题:宠物狗的计算能力、分类垃圾的能力该怎么办呢?

猫狗案例的分析思路:

1、从具体到抽象`分析`:从具体的DogCatTiger类出发
- a 、共性功能 抽取出Animal抽象类
- b 、特有功能 本类中定义
- c 、扩展功能 定义接口  (实际开发中,特有功能和扩展功能很难区分)

2、从抽象到具体`实现`:从抽象的Animal和接口依次定义,并在定义具体类时,写出继承自何处,实现了哪些接口

3、编码

通过上述分析思路,改进猫狗案例:

猫狗案例 version 7.0(接口interface-扩展功能)

abstract class Animal
{
	public Animal() { }
	public abstract void eat();
	public abstract void sleep();
}

class AnimalTool
{
	private AnimalTool() { }
	
	public static void useAnimal(Animal a)
	{
		a.eat();
		a.sleep();
	}
}

interface Calculable
{
	public abstract void clac();
}

interface GarbageClassable
{
	public abstract void gc();
}

class Cat extends Animal
{
	public Cat() { }
	
	public void eat()
	{
		System.out.println("Cat.eat");
	}
	public void sleep()
	{
		System.out.println("Cat.sleep");
	}
}

class Dog extends Animal
{
	public Dog() { }
	
	public void eat()
	{
		System.out.println("Dog.eat");
	}
	public void sleep()
	{
		System.out.println("Dog.sleep");
	}
}

class PetDog extends Dog implements Calculable,GarbageClassable
{
	public PetDog() { }
	@Override
	public void clac()
	{
		System.out.println(this.getClass().getName()+" calculate!");
	}
	
	@Override
	public void gc()
	{
		System.out.println(this.getClass().getName()+" for garbage collection!");
	}
}

class Tiger extends Animal
{
	public Tiger() { }
	
	public void eat()
	{
		System.out.println("Tiger.eat");
	}
	public void sleep()
	{
		System.out.println("Tiger.sleep");
	}
	
	public void hunting(Animal a)
	{
		// 反射和链式编程,暂不需要了解
		System.out.println(this.getClass().getName()+" Hunting "+a.getClass().getName());
	}
}

class Demo
{
	public static void main(String[] arg)
	{
		PetDog pd = new PetDog();
		pd.clac();
		pd.gc();
	}
}

22、深入形式参数和返回值问题

A:形式参数:
1、基本数据类型
2、引用数据类型

  • a、形式参数为数组,就是具体的数组对象
  • b、类作为形式参数:实际上需要的是类的对象
  • c、抽象类作为形式参数:实际上需要的是抽象类的具体子类对象
  • d、接口作为形式参数:实际上需要的是实现类的对象

B:返回值:
1、基本数据类型
2、引用数据类型

  • a、返回值类型为数组,就是具体的数组对象
  • b、返回值类型为类:实际上返回的是类的对象
  • c、返回值类型为抽象类:实际上返回的是抽象类的具体子类对象
  • d、返回值类型为接口:实际上返回的是实现类的对象

23、链式编程(方法链)

对象每次调用完成之后,返回的仍是一个对象。这样依次的调用,直到完成指定功能为止。也叫做 方法链。

new StringBuilder().append().append().append()...
// StringBuilder的append方法的返回值是StringBuilder的对象

this.getClass().getName();
// 返回当前对象的类名称,关于反射
// this.getClass()  返回值是 Class<T> ;
// getName 是 Class<T> 的获取名称的方法

24、包(package)

A:其实就是一个文件夹
B:作用:

  • a、把相同名称的类放置在不同的包中,加以区分
  • b、对类进行分类管理

案例:学校教务系统
1、学生:增加、删除、修改、查询
2、教师:增加、删除、修改、查询

方案:
A:按功能分

  • cn.sspu.add
    • addStudent / addTeacher
  • cn.sspu.delete
    • delStudent / delTeacher
  • cn.sspu.update
    • updateStudent / updateTeacher
  • cn.sspu.find
    • findStudent / findTeacher

B:按模块分

  • cn.sspu.Student
    • addStudent
    • delStudent
    • updateStudent
    • findStudent
  • cn.sspu.Teacher
    • addTeacher
    • delTeacher
    • updateTeacher
    • findTeacher

结合方案:先按模块分,模块中按功能分

  • cn.sspu.Teacher
    • add
    • del
    • update
    • find
      • findById
      • findByName
      • findByTitle

定义格式package 包名; (全部小些,中间使用“.”分割)

注意事项

  • A:package语句必须是程序的第一条可执行代码(注释除外)
  • B:package语句在同一个Java程序中,只可以有一个
  • C:没有package语句,程序默认无包名

带包的编译和运行

  • A:手动式:

    • a、javac编译当前带包的java文件
    • b、手动建立包对应文件夹,依次是一级文件夹、二级文件夹、三级文件夹…
    • c、将生成的字节码文件放置于b中最底层文件夹下
    • d、带包执行 : java 包名.类名
      在这里插入图片描述
  • B:自动式

    • a、javac编译当前带包的java文件 javac -d . Demo.java (在当前文件夹自动生成包文件夹) javac -d 目录 java文件
    • b、带包执行 : java 包名.类名
       

不同包下类之间的访问

现在我们重新将之前定义的猫狗案例用包来管理,置于com.rupeng.zoo包下,而将测试的Demo 放置于com.rupeng.test下。

常见问题及解决方法如下:
在这里插入图片描述
在这里插入图片描述

25、import 关键字

引入:不同包下的类之间的访问,每次使用需要使用全部路径到类,正如上述的PetDog的对象建立。太过于麻烦,所以Java提供了一个叫做 import的关键字来实现包的导入。

格式import 包名;

注意:推荐导入包的时候,写到类名,import java.util.Scanner ;而不是使用 通配符 * 导入包下所有类 import java.util.*;,这样的话需要全部遍历包中类,效率太低。

面试题

1、package、import、class之间有顺序关系吗?

package必须在Java程序的可执行第一条语(注释除外),之后是 import,在下面才是class的定义。而且,package只可以有一个,import可以有多个,class也可以由多个,建议只有一个。


2、比较四种修饰符:public、protected、默认private。

在这里插入图片描述

26、常见的修饰符

  • 权限修饰符:public、protected、默认、private
  • 状态修饰符:static、final
  • 抽象修饰符:abstract

A:类(class)

  • 权限修饰符:public、protected、默认、private(内部类)
  • 状态修饰符:final、static(内部类)
  • 抽象修饰符:abstract
  • 常见用:public

B:成员变量(member variable)

  • 权限修饰符:public、protected、默认、private
  • 状态修饰符:static、final
  • 抽象修饰符:/
  • 常见组合:private

C:构造方法(construct)

  • 权限修饰符:public、protected、默认、private
  • 状态修饰符:/
  • 抽象修饰符:/
  • 常见组合:publicprivate

D:成员方法(member method)

  • 权限修饰符:public、protected、默认、private
  • 状态修饰符:static、final
  • 抽象修饰符:abstract

E:常见组合规则

  • 成员变量:public static final(接口)
  • 成员方法:
    • public abstract(接口、抽象类)- 抽象方法(无方法体)
    • public static - 类名(或对象名)调用的静态方法
    • public final - 不可重写的终态方法

回顾一下不可与abstract一起使用修饰符

  • private 冲突
  • final 冲突
  • static 无意义

另:private static 基本不可同时使用(static使用类名调用,private只可类中访问),但是可以定义私有静态内部类。

27、内部类(inner class)

定义:面向对象设计语言中,定义在另一个类中的类,叫做内部类。主要有两种,一种是静态内部类(也叫嵌套类nested class);另一种是非静态内部类(也叫内部类inner class)。

内部类

  • a、类中成员位置的成员内部类
  • b、类中(方法内)局部位置的局部内部类(作用域{}
  • c、仅创建一次对象的局部内部类 - 匿名内部类(类似匿名对象)

优点:命名控制(类似于包)和 重点讲解的访问控制

*1、静态内部类(Static Nested Class)

定义:定义在外部类中,任何方法外。实际就是成员位置,与外部类的属性和方法并列。用static定义。有时候,使用内部类,只是为了把一个类藏在另一个类的内部,且不需要内部类引用其外部类的对象,为此将内部类使用static修饰,以便取消产生的引用。

特点

  • 静态内部类只可以直接访问外部类的静态成员(static)(new Outer().show();–创建对象调用)
  • 静态内部类中可以定义静态和非静态成员
  • 访问静态内部类的方法:
    • 静态方法:Outer.Inner.show();
    • 非静态方法:Outer.Inner in = Outer.Inner(); in.show();
      [new Outer.Inner().show();]

区别于成员内部类:生成一个静态内部类不需要外部类对象。静态内部类的对象可以直接生成:Outer.Inner in = new Outer.Inner(); 而不需要通过先创建外部类对象。这样实际上使得静态内部类成为了顶级类。也可以定义私有静态内部类(private static class Inner)。

注意:当类与接口(或接口与接口)发生方法命名冲突时,此时使用内部类来实现。用接口不能完全实现多继承,用接口配合内部类才能实现真正的多继承。

/*
  对于类与接口,拥有同名方法 run,有一个类Robot:
  class Robot extends Human implements Machine
  此时run()不可直接实现,编码如下(私有成员内部类案例):
*/
class Human
{
  public void run()
  {
    System.out.println("Run");
  }
}

interface Machine
{
  public abstract void run();
}

class Robot extends Human
{
  private boolean heartRun = false;

  private class MachineHeart implements Machine
  {
    public void run()
    {
      System.out.println("Heart run");
      heartRun = true;
    }
  }
  
  public void run()
  {
    if(!heartRun)
    {
      throw new RuntimeException("Heart should run,but not!");
    }
    System.out.println("Robot run");
  }

  public Machine getMachine()
  {
    return new MachineHeart();
  }
}

class RobotDemo
{
  public static void main(String[] args)
  {
    Robot robot = new Robot();
    Machine m = robot.getMachine();
	// m.run();
    robot.run();
  }
}
*2、成员内部类(member inner class)

定义:定义在外部类中,任何方法外(实际就是成员位置,与外部类的属性和方法并列)。

访问:内部类和外部类的成员可以共存(即使是同名,就近原则)

  • 内部类访问自身成员:this.成员
  • 内部类访问外部类成员:外部类.this.成员

特点

  • 1.成员内部类作为外部类的成员,可以访问外部类的所有属性和方法(私有也可以)(即使将外部类声明为private,但是对于处于其内部的内部类还是可见的)。

  • 2.用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。

注意:内部类是一个编译时现象,与虚拟机无关,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。

  • 3.成员内部类不能定义静态类成员,只能定义非静态的对象(实例)成员。

建立内部类对象时应注意:
在外部类的内部可以直接使用inner s=new inner();(因为外部类知道inner是哪个类,所以可以生成对象);而在外部类的外部,要生成(new)一个内部类对象,需要首先建立一个外部类对象(外部类可用),然后在生成一个内部类对象。

Outer o=new Outer();
Outer.Inner in=o.new.Inner();
// 等价于下面这一句
Outer.inner in = new Outer().new Inner();

注意:创建成员内部类实例时,外部类的实例必须已经存在。静态内部类则不依赖于外部类对象。

常见修饰符

  • private 为了保证数据安全性(提供更小的访问权限)
  • static 为了方便访问类的数据
  • private static 私有静态内部类
*3、局部内部类(local inner class)

定义:在方法内声明的,并且在该方法中创建一次对象的类

访问

  • 直接访问外部类的成员(包括私有)
  • 外部类如果想要访问内部类成员,需要在局部位置内部类定义之后创建对象才可以访问

特点

  • 局部内部类只可以使用默认修饰符(private、public、protected均不可)
  • 局部内部类中不可以定义static成员(属性和行为均不可以)
  • 局部内部类的作用域被限定在声明这个局部类的块中{}

优点:

  • 对外部世界是完全隐藏起来的,即使实在外部类的其他方法中。
  • 不仅可以访问外部类成员(含私有),还可以访问局部变量,不过访问的局部变量必须是final修饰的。使得局部变量和在局部类中建立的拷贝保持一致。

再谈final关键字:

  • final可以用来修饰局部变量、成员变量、静态变量。所有情况下的含义是:在创建这个变量之后,只可以为其赋值一次。此后再也不可修改它的值,这就是final。不过在定义final的时候,不必进行初始化,通常被称为空final变量。只要在使用前进行初始化就可以了。类中成员位置的final则要求必须在构造方结束法之完成初始化。
  • final修饰的基本数据类型的变量,赋值之后数值不可变;final修饰的引用数据类型变量,赋值之后地址值不可变,其内容数据可变的。
  • 比如:在局部类的中需要访问一个计数器的局部变量?创建一个final int类型的局部变量,就不可以加 1 了;于是我们创建一个只有一个元素的int类型的数组,但是这仅仅代表着它不可指向另一个数组,但是元素的值依然可以改变。

从这里,我们就可以渐渐地体会到java设计的强大之处:先定义规则,遵守规则,在规则内创建特殊,打破规则。

*4、匿名内部类(anonymous inner class)

定义:将局部内部类的使用再深入一下。假如只是创建这个类的一次对象,就不必命名了。这就是匿名内部类(anonymous inner class)。

格式

new SuperType(construction parameters)
{
  inner class methods and data
}
SuperType 可以是接口、类;
methods 基本上是重写方法(Override

实质是:继承了该类或实现了该接口的子类或实现类。

应用

  • 前提1、形式参数是引用数据类型
  • 前提2、明确匿名内部类的本质
  • 前提3、匿名对象的应用和优点(作为实际参数 / 仅仅只为了调用一次方法)

结合3个前提,引出匿名内部类在开发中的应用:一次或者很少次的使用类时。
优点:堆内存中,用完就是垃圾,垃圾回收期再空闲时自动回收,提高内存利用率。


内部类的优点:

1、内部类对象可以访问创建它的对象的外部环境,包括私有数据;(对比 :JS闭包的定义)
2、内部类不为同一包的其他类所见,具有很好的封装(安全)性(可定义比private更加小的访问权限);
3、使用内部类可以很方便的编写事件驱动程序;
4、匿名内部类可以方便的定义运行时回调,提高堆内存的利用率;
5、内部类可以方便的定义

面试题

1、完善代码,打印出 30 20 10 0(不考虑换行)

class OuterOuter
{
	public int num = 0;
	class Outer
	{
	  public int num = 10;
	  class Inner
	  {
	    public int num = 20;
	    public void show()
	    {
	      int num = 30;
	      System.out.println(__a__);  // 就近原则
	      System.out.println(__b__);   // 成员变量  this关键字
	      System.out.println(__c__);
	      System.out.println(__d__);
	    }
	  }
	}
}

class Demo
{
  public static void main(String[] args)
  {
    new OuterOuter().new Outer().new Inner().show();
  }
}

解答:

a:num,就近原则,局部变量num的值 30
b:this.num,也就是Inner.this.num ,内部类Inner的成员变量 num的值 20
c:Outer.this.num,外部类的成员变量 num的值 10
d:OuterOuter.this.num,外部类的成员变量 num的值 10
PS: c 处也可以创建Outer的对象访问 new Outer().num

this 实际上就是类名.this


2、局部内部类访问局部变量的注意事项? 【答案 上文中】


3、完善代码,输出“HelloWorld”

interface Inner
{
	void show();
}

class Outer
{
	// 代码填充区---top
	// 接口作为返回值类型,实际返回的是接口的实现类对象
	static Inner method()
	{
		/*  成员内部类
		class InnerImpl implements Inner
		{
			public void show()
			{
				System.out.println("HelloWorld");
			}
		}
		return new InnerImpl();   
		*/

		// 匿名内部类 
		return new Inner(){
			@Override
			public void show()
			{
				System.out.println("HelloWorld");
			}
		};
	}
	//代码填充区---bottom
}

class HWDemo
{
	public static void main(String[] args)
	{
		Outer.method().show();
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老坛算粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值