基础】02"/>
【JavaSE基础】02
原文写于 2016 年,个人学习笔记,闲来无事,搬运至此,希望于各位有用。主要内容是:面向对象的基础概念、三大特性、链式编程等。
当年真的好有毅力,一字一字敲,一图一图画。
文章目录
- 面向对象(Object-Oriented Programming)
- 1、面向对象概念
- 2、面向对象特点
- 3、面向对象开发、设计、`特征`
- 4、类(与对象)
- 5、类的内存图解
- `6、成员变量和局部变量的区别`
- 7、形式参数
- 8、匿名对象
- 9、三大特征之--封装(encapsulation)
- 10、private关键字
- 11、this关键字
- 12、构造方法
- 13、类的初始化
- 14、static关键字
- 15、静态变量和成员变量的区别
- main方法详解 | 自学类的过程
- *1、main方法详解
- *2、工具类帮助文档的制作
- *3、说明书的制作过程
- *4、工具类和说明书的使用
- *5、Java API
- *6、通过API学习使用Math类
- 16、代码块(code block)
- 面试题
- 17、三大特征之--继承(inheritance)
- 面试题
- 18、final关键字
- 面试题
- 19、三大特征之--多态(polymorphism)
- 20、抽象类(abstract class)
- 面试题
- 21、引用数据类型之三:接口(interface)
- 面试题
- 22、深入形式参数和返回值问题
- 23、链式编程(方法链)
- 24、包(package)
- 25、import 关键字
- 面试题
- 26、常见的修饰符
- 27、内部类(inner class)
- *1、静态内部类(Static Nested Class)
- *2、成员内部类(member inner class)
- *3、局部内部类(local inner class)
- *4、匿名内部类(anonymous inner class)
- 面试题
面向对象(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 0Huawei 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指向pPhone 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 0Huawei Write 1799Huawei Write 1799Apple Golden 5899Apple 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: ChinaName:Joian,Age:25,Country: ChinaName:Yzk,Age:18,Country: USAName: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 falsehello 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){// 若指定值在数组中不存在,返回 -1int 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();}
}>>> 302010
// 注释掉 (1)
>>> 202010
// 注释掉 (1) (2)
>>> 101010
// 注释掉 (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();}
}>>> YXYZ
说明:虽然子类中每一个构造方法中默认第一条语句都是
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();}
}>>> YXYGFZP
说明:分层初始化时,从子类代码中直接找到 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.静态代码快0Demo.静态代码快0Demo.静态代码快100Demo.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();}
}>>> 错误: 可能尚未初始化变量numpublic 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.ConstructorY.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.eatCat.sleepDog.eatDog.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.eatCat.sleepCat.eatCat.sleepCat.eatCat.sleepDog.eatDog.sleepDog.eatDog.sleepDog.eatDog.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.eatCat.sleepCat.eatCat.sleepCat.eatCat.sleepDog.eatDog.sleepDog.eatDog.sleepDog.eatDog.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.eatPig.sleepWolf.eatWolf.sleepTiger.eatTiger.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() { }// 使用 useAnimalpublic 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.eatCat.sleepDog.eatDog.sleepPig.eatPig.sleepWolf.eatWolf.sleepTiger.eatTiger.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() { }@Overridepublic void clac(){System.out.println(this.getClass().getName()+" calculate!");}@Overridepublic 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 包名.类名
- a、javac编译当前带包的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(){@Overridepublic void show(){System.out.println("HelloWorld");}};}//代码填充区---bottom
}class HWDemo
{public static void main(String[] args){Outer.method().show();}
}
更多推荐
【JavaSE基础】02
发布评论