原文写于 2016 年,个人学习笔记,闲来无事,搬运至此,希望于各位有用。主要内容是:面向对象的基础概念、三大特性、链式编程等。
当年真的好有毅力,一字一字敲,一图一图画。
面向对象是相对于面向过程而言的,面向对象和面相对过程都是对于代码的组织方法,编程思想
。
(1)、面向过程:强调的是功能行为。代表语言是:C语言。
示例:将大象装进冰箱。
打开、进去、关闭
都是功能行为,在代码中的直观体现就是函数或方法。这就是一种面向过程的以功能行为为主体的思想体现。
(2)、面向对象(Object-Oriented Programming):将功能封装进对象,强调的是具备了功能的对象。
示例:将大象装进冰箱。
具体分析:
P.S:从上述的分析过捏中,可以看到即使是面向对象,其类中的功能定义,也是基于面向过程的编程思想。
也就是说:面向对象是基于面向过程的编程思想,而不是面向过程的替代,两者相辅相成
。
面向对象是一种符合人类习惯思维的编程思想,是一种能够将复杂性的问题简单化、将程序员从执行者变成指挥者(角色转换)的编程思想。
万事万物皆对象
为了提高代码的复用性,将2次以上重复的代码块封装成方法。–面向过程
为了将完成指定功能的代码/方法集中,提高代码的可用性、复用性,使用对象改进。-- 面向对象
完成需求时:
从而简化开发,提高复用。
特征
使用计算机语言其实就是不断的描述事物。
Java中描述事物通过类来实现,类是具体事物的抽象,是概念级的意义。
对象即是该类事物实实在在存在的个体。
类(class)是Java中最小的基本单元
可以理解成:类就是图纸,汽车就是堆内存中的对象。
定义
类(class)
:类就是对现实社会中同一类事物的抽象,是一组相关的属性和行为的集合,是一个抽象的概念。对象(object)
:对象是现实社会中实实在在存在的事物,是该类事物的具体表现形式、具体存在的个体。抽象(abstarct)
定义为类(class)
**属性
** 抽象为类的**成员变量(member variable)
**成员方法(member function)
**综上,类主要有成员变量和成员方法组成,后面还会具体包括其他的组成部分(譬如:构造函数)
定义类,实际上就是在定义类中的成员
创建对象的格式:
调用类中成员的格式:
三种主要的调用方式:单独调用、输出调用、赋值调用
示例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
形参是引用类型的时候该如何调用呢
?
示例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
引用类型作为参数的时候,传递的是具体的
对象,而不是抽象的类
定义:没有指定对象名的对象,叫做匿名对象。譬如: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
继续之前的示例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修饰的成员变量等都是封装的体现
。
好处
:
将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:可以用来修饰成员(成员变量、成员方法等)
C:被其修饰的成员,只可以在本类中访问
常见应用:
A:将成员变量用private修饰
B:提供公共的访问方式
// A:将成员变量用private修饰
private String name;
// B:提供公共的访问方式
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
定义
:当前类的对象引用
应用
:解决局部变量隐藏成员变量的问题
// 成员变量
private String name;
public void setName(String name)
{
// 使用哪个对象来调用setName方法。这里的this就是那个对象
this.name = name;
}
理解
:方法被哪个对象调用,this
就指向那个对象
作用
:给对象的数据(成员)初始化
格式
:
注:任何void方法都可以用return; ,只是通常情况下,习惯省略
注意
:
建议永远自己给出无参构造。
成员变量的赋值
:
// 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;
}
类的组成
:成员变量、成员方法、构造方法
成员方法分类
:
练习:示例4中的Student就是一个基本类的标准写法,请参考写出Phone的标准写法
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文件时,会自动产生两个方法,一个是类的初始化方法, 另一个是实例的初始化方法
:在jvm第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行。
:在实例创建出来的时候调用,包括调用new操作符;调用Class或java.lang.reflect.Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;通过java.io.ObjectInputStream类的getObject()方法反序列化。
结合之前的所有的知识
完成以下的练习:
思考一下:能否将进行四则运算的a、b定义为MyMath的成员变量呢?
答案是否定的
,首先来看一下类的定义:描述同一类事物的属性和方法的集合,很显然Tools和MyMath只是一个工具类,不需要参与运算的a、b作为它们的属性。
练习总结
:
简单介绍一下import
关键字,用于导包的关键字,使用时候,注意一定要在所有类的前面
思考一下:练习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)是相同的概念
静态方法中:
只能访问静态的成员变量、成员方法
非静态方法中:
可以访问静态或非静态的成员变量、成员方法
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
测试类的作用:创建其它类的对象,并调用其它类的功能。回顾之前一维数组的常见操作,当时都是定义在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()
{}
}
工具类 ==> 文档注释 ==> 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自上而下的编译机制,可能后面数不清楚的错误只是因为最上面漏写的一个分号";"
。
Oracle提供的API规范:Application Programming Interface(应用程序编程接口,帮助文档)
API的使用:
示例练习:猜数字小游戏(使用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;
}
}
}
}
Java中使用{ }
括起来的代码称为代码块
(1)、根据位置和声明不同,分成以下几类:
static
修饰,为了对类进行初始化,类加载就执行,并且只执行一次
。(2)、格式:
{ }
括起来,每次调用构造方法之前,都会先执行。{ }
括起来,使用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中存在着构造代码块和构造方法,也不会执行,因为没有创建对象,也不可能为测试类创建对象。
Student.class和Teacher.class两个不同的类,可能会有什么是一样的呢?
这样的重复,在定义类的时候,是需要书写两次的,如果此时再有教务员类,就需要再书写一次。在编程时候,这会造成代码量的重复增加。回想一下,方法的定义原则(当一个代码块,出现两次以上,就需要考虑定义方法),那么如何在类间消除代码的重复呢?这就引入了面向对象三大特征之一的继承(inheritance)。
定义
:多个类中存在相同的属性和行为时,将这些内容抽取到一个独立类中,将现有类和独立类之间建立一个关系,使得现有类可以具有独立类的内容。这个关系就是继承(inheritance)。其中,抽取出来的独立类,叫做超类(super class),也称为父类或基类;而继承自它的现有类称为派生类或子类。子类在继承父类的基础上可以定义自己的新成员。
格式: class 子类 extends 父类
优点
- A:提高代码的复用性
- B:提高代码的可维护性
- C:让类间产生关系,是多态(polymorphism)的前提
缺点:类的耦合性紧密
PS
:软件开发的原则:提高模块独立性(松散耦合、高度内聚)
1、耦合性:模块之间的联系紧密程度的定性度量
2、内聚性:模块内部的各成分之间的联系紧密程度
特点
在java中这个是不被允许的。Java中extends关键字之后只可以出现一个父类
// Java中多重继承体系
class Father extends GrandFather{}
class Son extends Father{}
注意事项
缺点:打破了封装性
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)
>>>错误: 找不到符号
示例2:继承中构造方法的访问问题
class Father
{
private String name;
public Father(String name)
{
this.name = name;
}
}
class Son extends Father
{
public Son() {}
}
请根据提供的解决方案修改代码。使其可以通过编译。
在上面问题中,我们获取了一个简单的结论,那就是:类若存在父类,就需要先加载父类,再加载子类。 那么,一个类是如何初始化的呢?
明确以下几点
:
成员变量的初始化顺序
:代码快执行顺序
:一句话总结
:子父类-分层初始化
一个类的初始化过程
示例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 可以改变返回值类型吗?
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]。
首先,我们先来回顾一下方法覆盖(Overriding):继承中,子类中有和父类一摸一样的方法声明的现象。但是,若我只是想要子类去继承就可以了,而不想要有任何的可能的修改,要怎么做呢?
因此,JAVA提供了一个关键字final
去解决这一类的问题。
final
的应用:
下面我们来回顾一下,在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变量完成一次显示赋值
定义
:某事物在不同时刻表现出来的不同的状态
前提条件:
示例:
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方法的,所以才会提示错误
那么如何解决呢?
解决方案:
不使用
) Tiger t1 = new Tiger();
t1.hunting(new Dog());
把父类引用强制转换为子类引用(向下转型 downcasting)
Tiger t = (Tiger)(t1);
t.hunting(new Dog());
对象之间的转型问题::
Animal t1 = new Tiger();
向上转型(upcasting)并非是将子类自动向上转型为父类对象,相反它是从另一种角度去理解向上两字的:它是对父类对象的方法的扩充,即父类对象可访问子类从父类中继承来的和子类复写父类的方法。
其它的方法都不能访问
,包括父类中的私有成员方法、子类中特有的方法。
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" 父类的继承关系。
abstract class Animal
{
public Animal() { }
public abstract void eat();
public abstract void sleep();
}
上面是之前我们为了实现多态,而抽取出来的抽象类Animal。现在我们详细来解释一下。
定义:
抽象类往往是用来表征对问题域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。在Java中,一个没有方法体的方法,应该被定义成抽象方法。而包含抽象方法的类就是抽象类(abstract class
)。
特点:
abstract
)关键字来修饰抽象类不一定有抽象方法,包含抽象方法的类,一定要是抽象类。
注意区别:没有方法体(void eat();
)和空方法体(void eat(){}
)
抽象不可以实例化,但有构造方法,用于子类访问父类的初始化。
- 子类是抽象类(使用abstract修饰)。不用重写父类所有的抽象方法,可以留给子类的子类去实现。可以重写部分的抽象方法,但是依然不可实例化。
- 子类是具体类(不使用abstract修饰)。必须重写父类所有的抽象方法,非抽象方法可以继承也可以重写。
抽象类的实例化是依靠具体类来实现的。是多态的形式:Animal a = new Cat();
抽象类成员特点
可以是变量,也可以是自定义常量(final修饰)
无参、带参均可。用于子类访问父类的初始化
super(); super(...);
- 抽象方法:强制子类必须做的(不一样的事情),子类具体类必须实现所有抽象方法。子类若是抽象类,则是子类的子类实现…
- 非抽象方法:子类继承的方法(一样的事情),提高代码的复用性
1、一个类若无抽象方法,是否可以定义为抽象类?有什么意义?
可以定义为抽象类,意义在于不让其实例化。其中的方法主要用来给子类去继承或者重写调用,提高代码的复用性。
2、abstract不可以和哪些关键字共存?
- A:
private关键字
原因:冲突。抽象的主要目的是延迟到子类中实现,使用private之后子类无法访问。- B:
final关键字
原因:同上。- C:
static关键字
原因:无意义。static关键字主要是通过类名调用的,abstract修饰的方法是没有方法体的,因此毫无意义。
狗自古以来是人类最好的朋友。宠物狗更是作为家庭的一员一起陪着我们,为了开发他们的智力,宠物之家的工作人员总是会给我们很多的惊喜,它们学会了算术、学会帮我们分类生活垃圾。那么问题来了,这些后天的能力
该定义在什么地方呢?
首先,我们可能想到建立在Dog类中,毕竟是Dog是我们最好的朋友。但是,难道Cat、Tiger就不可驯化吗?于是我们又把这些功能定义在了Animal类中,发挥继承的作用。然而,问题往往经不起深究,这些东西是用来描述Animal类的吗?所有动物都会被驯化吗?(你让野猪怎么想?不被驯化,就不是动物了吗?)额···,不是!遇到了人生瓶颈期了,有木有?回想一下,似乎我们还没有竭尽全力。我们想到把这些新的东西放到最上层得抽象父类Animal中、尝试过把它们定义在具体的子类Cat、Dog、Tiger中,为什么不创建新的东西呢?毕竟创新才是出路。重点是如何创新?先来学习一些新的知识。永远记住一个编程的真理:TO LEARN A LITTLE KNOWLEDGE, WRITE A LINE OF CODE ...
定义
:为了体现事物功能的扩展性,Java中定义了接口去实现这些额外功能,并不给出具体的实现。
特点
:
格式:
interface 接口名 { }
格式:
class 类名 implements 接口名 { }
抽象类(无意义)
具体类:必须重写父接口中所有抽象方法
接口成员特点
:
只可以是静态自定义常量(默认是
public static final
,建议自己给出)
无构造方法! 那么子类如何实现父类的初始化呢?
- Java中的所有类都默认继承自
java.lang.Object
类,该类只有无参构造(所有子类中构造方法默认第一条语句是super();
的缘由所在),没有成员变量。类Object是类层次结构中的根类,每个类都是用它作为超类。
接口中成员方法,不可以有方法体,只可以是抽象方法(默认修饰符是
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();
}
}
A:形式参数:
1、基本数据类型
2、引用数据类型
B:返回值:
1、基本数据类型
2、引用数据类型
对象每次调用完成之后,返回的仍是一个对象。这样依次的调用,直到完成指定功能为止。也叫做 方法链。
new StringBuilder().append().append().append()...
// StringBuilder的append方法的返回值是StringBuilder的对象
this.getClass().getName();
// 返回当前对象的类名称,关于反射
// this.getClass() 返回值是 Class ;
// getName 是 Class 的获取名称的方法
A:其实就是一个文件夹
B:作用:
对类进行分类管理
案例:学校教务系统
1、学生:增加、删除、修改、查询
2、教师:增加、删除、修改、查询
方案:
A:按功能分
B:按模块分
结合方案:先按模块分,模块中按功能分
定义格式
:package 包名;
(全部小些,中间使用“.
”分割)
注意事项
:
带包的编译和运行:
A:手动式:
B:自动式
javac -d . Demo.java
(在当前文件夹自动生成包文件夹) javac -d 目录 java文件
java 包名.类名
不同包下类之间的访问:
现在我们重新将之前定义的猫狗案例用包来管理,置于com.rupeng.zoo包下,而将测试的Demo 放置于com.rupeng.test下。
引入
:不同包下的类之间的访问,每次使用需要使用全部路径到类,正如上述的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。
A:类(class)
public
B:成员变量(member variable)
private
C:构造方法(construct)
public
、private
D:成员方法(member method)
E:常见组合规则
public static final(接口)
public abstract(接口、抽象类)- 抽象方法(无方法体)
public static - 类名(或对象名)调用的静态方法
public final - 不可重写的终态方法
回顾一下不可与abstract一起使用修饰符
- private 冲突
- final 冲突
- static 无意义
另:
private static
基本不可同时使用(static使用类名调用,private只可类中访问),但是可以定义私有静态内部类。
定义
:面向对象设计语言中,定义在另一个类中的类,叫做内部类。主要有两种,一种是静态内部类(也叫嵌套类nested class);另一种是非静态内部类(也叫内部类inner class)。
内部类
:
{}
)优点
:命名控制(类似于包)和 重点讲解的访问控制
。
定义
:定义在外部类中,任何方法外。实际就是成员位置,与外部类的属性和方法并列。用static定义。有时候,使用内部类,只是为了把一个类藏在另一个类的内部,且不需要内部类引用其外部类的对象,为此将内部类使用static修饰,以便取消产生的引用。
特点
:
区别于成员内部类
:生成一个静态内部类不需要外部类对象。静态内部类的对象可以直接生成: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();
}
}
定义
:定义在外部类中,任何方法外(实际就是成员位置,与外部类的属性和方法并列)。
访问
:内部类和外部类的成员可以共存(即使是同名,就近原则)
特点
:
1.成员内部类作为外部类的成员,可以访问外部类的所有属性和方法(私有也可以)(即使将外部类声明为private,但是对于处于其内部的内部类还是可见的)。
2.用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
注意
:内部类是一个编译时现象,与虚拟机无关,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
建立内部类对象时应注意:
在外部类的内部可以直接使用inner s=new inner();(因为外部类知道inner是哪个类,所以可以生成对象);而在外部类的外部,要生成(new)一个内部类对象,需要首先建立一个外部类对象(外部类可用),然后在生成一个内部类对象。
Outer o=new Outer();
Outer.Inner in=o.new.Inner();
// 等价于下面这一句
Outer.inner in = new Outer().new Inner();
注意:创建成员内部类实例时,外部类的实例必须已经存在。静态内部类则不依赖于外部类对象。
常见修饰符
:
定义
:在方法内声明的,并且在该方法中创建一次对象的类
访问
:
特点
:
{}
优点
:
再谈final关键字:
空final变量
。只要在使用前进行初始化就可以了。类中成员位置的final则要求必须在构造方结束法之完成初始化。比如
:在局部类的中需要访问一个计数器的局部变量?创建一个final int
类型的局部变量,就不可以加 1 了;于是我们创建一个只有一个元素的int类型的数组,但是这仅仅代表着它不可指向另一个数组,但是元素的值依然可以改变。从这里,我们就可以渐渐地体会到java设计的强大之处:先定义规则,遵守规则,在规则内创建特殊,打破规则。
定义
:将局部内部类的使用再深入一下。假如只是创建这个类的一次对象,就不必命名了。这就是匿名内部类(anonymous inner class)。
格式
:
new SuperType(construction parameters)
{
inner class methods and data
}
SuperType 可以是接口、类;
methods 基本上是重写方法(Override)
实质是:继承了该类或实现了该接口的子类或实现类。
应用
:
结合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();
}
}