【JavaSE基础】02-面向对象

JavaSE进阶基础这一篇就够了,你值得拥有! 本Java视频教程适合有一定编程语言基础的学员观看,在本Java视频教程中讲解了Java开发环境搭建、Java基础语法、面向对象、异常、数组、常用类、集合、IO流、线程、反射机制。该Java视频教程为讲师上课实录,共60个课时(1小时为1个课时),讲师语言表达流利,思路清晰。如果您是有一门编程语言基础的,或者您现在是一名Java程序员且想巩固一下JavaSE的,请打开Java视频教程观看吧!... 阅读详情

原文写于 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、从具体到抽象`分析`:从具体的Dog、Cat、Tiger类出发
- 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
  • 状态修饰符:/
  • 抽象修饰符:/
  • 常见组合:public、private

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();
	}
}
《JavaSE系列教程》 本专栏内容是为零基础正在学习Java的学习者量身定制的学习路线及方案,本教程字数超20w+,插图超过400+,知识体系极其全面,案例极其丰富。本配备有专门的学习群及学习视频,读者可加学习群与正在学习该阶段的人一同学习,有任何不懂的也可以观看于此教程配套的学习视频。 阅读详情

相关推荐

PIDNet实战:如何在Cityscapes数据集上实现93.2 FPS的实时语义分割(附完整训练代码)

本文提供了在Cityscapes数据集上实现实时语义分割模型PIDNet的完整工程实践指南。PIDNet凭借其独特的比例-积分-微分三分支架构,在CVPR 2023上取得了速度与精度的出色平衡。文章详细介绍了从环境配置、数据预处理、模型训练到性能优化与部署的全流程,并分享了实现93.2 FPS高推理速度的关键技巧与代码。

weixin_29047919的博客 375

JavaSE基础复习02_Java基础&&面向对象

1. JDK,JRE,JVM三者之间的关系,以及JDK、JRE包含的主要结构有哪些 ​ JDK = JRE + Java的开发工具(javac.exe,java.exe,javadoc.exe) ​ JRD = JVM + Java核心类库 2. 常用的几个命令行操作都有哪些?(至少4个) ​ cd:打开目录,改变盘符 ​ md:创建目录 ​ rd:删除目录 ​ del:删除文件 ​ cd…:返回...

SeafyLiang的博客 314

使用 Restart Extension 继续解决 Ansys 机械分析

您正在执行复杂或大型的多步有限元分析,突然,您意识到经过数小时的求解后,您需要添加或修改边界条件。在本篇博文中,您将了解如何利用 Ansys Mechanical 中的 RestartAnalysis 扩展来扩展多步分析,方法是合并额外的时间步骤和边界条件,而不会丢失已计算的结果。幸运的是,在 Mechanical 中,有一种直接的方法可以实现这一点,而不会损害您之前的结果,而这在添加新边界条件和使用重新启动点时经常发生。得到的结果符合预期,您可以看到施加的力产生的这两个部分之间的相对位移。

jkh920184196的博客 1233

JavaSE基础语法之面向对象与异常02

JavaSE基础语法之面向对象与异常处理

mzz1964019615的博客 285

车载摄像头+max96712 + rk3568 调试过程中遇到的问题

本文主要讲述本人在调试rk3568通过mipi-csi2链接max967812,max96712再外接4路车载摄像头过程中遇到的问题,以及最终如何解决这些问题。图中会贴上很多重要且关键的波形图,以便读者在调试过程中进行比对,分析。 如果在调试过程中,mipi总线上的波形没得什么问题的话,还是抓图失败的情况下,需要着重检查 频率,lane数,摄像头分辨率,摄像头输入图像格式等重要配置。

Amelio Ming 2066

记录思科cisco交换机升级iso步骤

Cisco Nexus 93180交换机版本升级

a635161812的博客 4377

JavaSE入门面向对象基础知识day02

本人自己学习java每天的知识点

dddddD4563256的博客 240

mmsegmentation教程1:自定义数据集、config文件修改、训练教程

mmseg使用教程

weixin_42748439的博客 2万+

02 - JavaSE之基础及面向对象(补充)

引用数据类型 java中的数据类型可以分为 基本数据类型 和 引用数据类型 两大类 int float char boolean等都是基本数据类型 类类型都是引用数据类型 引用数据类型类似C语言中的指针,多个引用数据类型可以引用同一个对象 基本数据类型的变量都有一份自己的值拷贝,而引用数据类型都是指向自己堆空间的值。 多态 java中的多态只能发生...

weixin_34037977的博客 115

图学习——04.HAN(异构图注意力网络)

什么是异构图,什么是元路径,什么是HAN,怎么计算attention,节点级别的attention和语义级别的attention

m0_51474171的博客 2999

Versal ACAP NoC实战:像理解计算机网络一样配置AXI NoC,打通PS与PL数据通路

本文深入探讨了Versal ACAP NoC的配置与优化,通过计算机网络类比解析NoC架构,详细介绍了在VD100开发板上配置AXI NoC实现PS与PL数据交互的实战步骤。文章涵盖NoC组件功能、Vivado配置技巧、性能优化方法及常见问题解决方案,帮助开发者高效打通Versal芯片的数据通路。

weixin_42665255的博客 293

JAVASE基础08

01-面向对象(数组工具对象建立) 面向对象思想是通过对象操作完成功能,在面向对象的·程序设计语言中必须有描述对象及其相互之间关系的语言成分。系统中一切事物皆为对象。 下面建立一个工具对象 public class ArrayTool { public int getIndex(int[] arr,int key) { for(int x=0; x<arr.length; x++) { if(arr[x]==key) return x; } return -1; } } 使用示范: class Ar

weixin_45532679的博客 234

系统架构设计师【第1章】: 绪论 (核心总结)

系统架构 (System Architecture)是系统的一种整体的高层次的结构表示,是系统的骨架和根基,也决定了系统的健壮性和生命周期的长短。系统架构设计师是承担系统架构设计的核心角色,他不仅是连接用户需求和系统进一步设计与实现的桥梁,也是系统开发早期阶段质量保证的关键角色。通俗地说,系统架构 (System Architecture)

数据知道的博客 1万+

JavaSE核心知识点02面向对象编程

本文主要介绍了Java面向对象编程(OOP)中的核心概念,包括类与对象、封装、继承和多态。类是对象的模板,定义了对象的属性和行为,而对象是类的具体实例。通过代码示例,文章详细讲解了如何定义类、创建对象、使用实例变量和静态变量,以及如何通过封装保护数据。此外,文章还介绍了构造方法的作用、继承的基本概念,以及对象在内存中的分配方式。最后,文章总结了封装、继承和多态这三大OOP特性,强调了它们在实际编程中的重要性,并提供了练习题目帮助读者巩固所学知识。通过本文,读者可以全面掌握Java面向对象编程的基础知识,为进

感谢阅读 1629

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

自学java开发从零基础到大神视频教程

百度网盘 java2017课件教程 Java开发全套 Java最新自学视频 全课程学费20000元,成为VIP会员,让你从零基础到大神~在家也能自学java开发     01-基础知识     02-面向对象编程     03-JavaSE-01     04-JavaSE-02     05-Oracle     06-JDBC     07-Web基础     08-JQuery     ...

weixin_44019542的博客 278

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

【JavaSE复习】基础、面向对象

两种格式1.格式1:数据类型[ ] 变量名,例如:int [ ] arr。定义了一个int类型的数组,数组名是arr。2.格式2:数据类型 变量名[ ], 例如:int arr[ ]。定义了一个int类型的变量,变量名是arr数组。无参数格式public stastic void 方法名(){…}有参数格式public static void 方法名(参数){… …}public static void 方法名(参数1,参数2,…){… …}带返回值格式。

m0_46261292的博客 1013

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

怎样正确的度过JavaSE阶段真的很重要!(详细知识内容+资料)

不要小看JavaSE的这些基础章节,打好基础很重要!巧了,我这里涵盖了所有Java基础!

奈何的人生 2万+

JavaSE 面向对象基础

JavaSE类和对象对象内存图成员变量和局部变量封装构造方法 类和对象 对象内存图 成员变量和局部变量 封装 构造方法

苦瓜的博客 3098

JAVASE基础之面向对象

JAVASE基础之面向对象1面向过程2面向对象3面向对象的特征3.1封装3.2继承3.3多态 1面向过程 概念:面对一个问题,考虑从顶向下逐步求精去分析问题和解决问题 特征:自顶向下逐步求精,不去考虑对象的存在 2面向对象 概念:面对一个问题,考虑解决该问题需要多少个对象,每个对象有哪些特征(属性、成员变量)和行为(方法),以及对象之间如何协作,最后调用对象的方法来解决问题 步骤:1 定义类 ​ ...

chenhao0922的博客 318

JavaSE 总复习:夯实基础,迈向进阶之路

在实际编程中,不断实践和深入理解这些概念,才能更好地运用 Java 语言解决各种复杂的问题,为后续的学习和项目开发奠定坚实的基础。例如:int[][] matrix = new int[3][3];String类提供了许多操作字符串的方法,如length()获取字符串长度,charAt()获取指定位置字符,substring()截取子字符串,equals()比较字符串内容等。:byte(1 字节,-128 到 127)、short(2 字节)、int(4 字节)、long(8 字节,需在数值后加L或l)。

2301_80350265学无止尽5的博客 5336

JavaSE之面向对象知识点总结

JavaSE之面向对象知识点总结

weixin_44528963的博客 1611

02-javase-面向对象-笔记

02-javase-面向对象-ydl学习笔记 文章目录02-javase-面向对象-ydl学习笔记一、面向对象概述1、面向对象介绍1.1、面向过程和面向对象1.2、用例子思考2、自己创建对象二、面向对象之封装(encapsulation)1、class和对象2、引用数据类型3、成员变量4、成员方法5、构造器6、this关键字7、setter和getter8、String详解9、包装类和自动拆装箱10、封装一个超级数据11、封装一个超级链表12、封装一个栈和队列13、银行取票机三、面向对象之继承(Inher

cygqtt的博客 1万+

JavaSE 基础语法、流程控制和方法、数组、面向对象、异常

java方法是语句的集合,它们在一起执行一个功能1方法是解决一类问题的步骤的有序组合2方法包含于类或对象中3方法在程序中被创建,在其他地方被引用设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,

Alacan的博客 954

JavaSE-面向对象之类与对象

权限控制符] class 类名 {//零个到多个构造器 * 构造器定义 * 注意:构造器无返回值类型[权限控制符] 类名(参数列表){};//零个到多个属性 * 属性定义[权限控制符] [static | final] 属性类型 属性名;//零个到多个方法 * 方法定义[权限控制符] [abstract | final] [static] 返回值类型 方法名(参数表){};提示:定义类的时候,不是必须显式写构造方法、属性、方法。【示例】:定义一个学生类。

qq_59737375的博客 2292

java基础(JavaSE)

JAVASE

weixin_46088280的博客 6204
上一篇: 【JavaSE基础】00-计算机基础及环境搭建
下一篇: 【JavaSE基础】03-常用类API(一)
老坛算粉
博客等级 码龄12年 708粉丝 73原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

老坛算粉

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

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

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

打赏作者

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

抵扣说明:

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

余额充值