面向对象的各种思想以及那些概念性的东西都结合示例给大家过了一遍,因为是用 Java 给大家展开的,但是 Java 光会那些东西,貌似还不能写代码,实际开发的时候还会用到接口、抽象类、匿名类 、枚举、反射这些 Java 面向对象的特性,这篇文章我们就一起再来学习一下进阶部分的内容,内容的大纲如下:

Object全名java.lang.Object,java.lang包在使用的时候无需显示导入,编译时由编译器自动导入。Object类是类层次结构的根,Java中所有的类从根本上都继承自这个类。
Object类是Java中唯一没有父类的类。其他所有的类,都继承了Object类中的方法(所有的类都隐式的继承自Object,如果你没有显式地给类指定父类时,编译器会给类加一个父类Object,如果你给它指定了父类,编译器就不会多此一举了)。
Object具有哪些属性和行为,是 Java 语言设计背后的思维体现。Object类没有定义属性,一共有13个方法,具体的类定义结构如下图:

对 equlas() 方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么? 如上,在 Object 类中,此标尺即为两个实例 "=="。当然,这个标尺不是固定的,其他类中可以按照实际的需要 重写 equals() 方法,对此标尺含义进行重定义。
比如 String 类中则是依据字符串内容是否相等来重定义了此标尺含义。如此可以增加类的功能性和实际编码的灵活性。当然了,如果类本身没有重写 equals() 方法来重新定义此标尺,那么默认的将使用其父类的equals()方法,直到 Object基类。
如下场景的实际业务需求,对于User类的对象,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。这时我们就可以重写 equals() 以重定义User对象相等的标尺。
- package com.corn.objectsummary;
-
- public class User {
-
- private int uid;
- private String name;
-
- public int getUid() {
- return uid;
- }
-
- public void setUid(int uid) {
- this.uid = uid;
- }
-
- protected String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null || !(obj instanceof User)) {
- return false;
- }
- if (((User) obj).getUid() == this.getUid()) {
- return true;
- }
- return false;
- }
- }
hashCode()方法返回一个整型数值,表示对象的哈希码值。
hashCode()具有如下约定:
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。
可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是充要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?
其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。以集合类 Set 为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将 Set 进行一次遍历,并逐一用 equals() 方法判断两个对象是否相等,此种算法时间复杂度为 O(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)
在此需要纠正一个理解上的误区:对象的hashCode() 返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode() 相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode() 返回的哈希码可能相同。
因此,在上一个例程中,重写了 equals() 方法后,需要重写 hashCode() 方法。
- package com.corn.objectsummary;
-
- import java.util.Objects;
-
- public class User {
-
- private int uid;
- private String name;
-
- public int getUid() {
- return uid;
- }
-
- public void setUid(int uid) {
- this.uid = uid;
- }
-
- protected String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
-
- @Override
- public boolean equals(Object obj) {
- if (obj == null || !(obj instanceof User)) {
- return false;
- }
- if (((User) obj).getUid() == this.getUid()) {
- return true;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(getName(), getUid());
- }
- }
toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:
- public String toString() {
- return getClass().getName() + "@" + Integer.toHexString(hashCode());
- }
toString()方法相信大家都经常用到,即使没有显式调用,但当我们使用System.out.println(obj)时,其内部也是通过toString()来实现的。
因为toString是Object里的方法,所以任何一个Java的对象,都可以调用这个方法,类一般会重写toString方法,定制自己对象的字符串表示形式。
其他几个Object里定义的方法等用到了再说,比如wait(...) / notify() | notifyAll()几个方法,都是 Java 线程操作相关的方法。
Class 类是代表类的类,每个Class类的示例,都代表了一个类。
上面一节说了,所有的Java类都是继承了Object这个类,在Object这中有一个方法:getClass(),这个方法是用来取得类型对应的Class类的对象。
我们自己无法生成一个Class对象(构造函数为private),而这个Class类的对象是在当各类被载入时,由 Java 虚拟机自动创建其 Class 对象。
获取Class类对象的方法:
- package com.example;
-
- import com.example.factory.Phone;
-
- public class UseClassObjAppMain {
- public static void main(String[] args) {
- Phone phone = new Phone(
- "手机001","Phone001",100, 1999, 999,
- 4.5,3.5,4,128,"索尼","安卓"
- );
-
- // 始祖 Object类里的getClass方法,可以得到Class 类的对象
- Class claz = phone.getClass();
- }
- }
- package com.example;
-
- import com.example.factory.Phone;
-
- public class UseClassObjAppMain {
- public static void main(String[] args) {
- Class claz = Phone.class;
- // Class claz = String.class
- // Class claz = int.Class
- }
- }
注意,使用这种办法生成Class类对象时,不会使JVM自动加载该类(如String类,而其他办法会使得JVM初始化该类。
通过一个类的 Class 实例,可以获取一个类所有的信息,包括成员变量,方法,等
- package com.example;
-
- import com.example.factory.Phone;
-
- public class UseClassObjAppMain {
- public static void main(String[] args) {
- Class claz = Phone.class;
- // 获取类的全限定名
- System.out.println(claz.getName());
- // 获取不包含包名的类名
- System.out.println(claz.getSimpleName());
-
- Field countField = clazz.getField("count");
-
- Method buyMethod = clazz.getMethod("buy", int.class);
- Method equalsMethod = clazz.getMethod("equals", Object.class);
- }
- }
这里介绍的主要是获取 Class 类对象的方式,Class 类对象更多的是在反射里使用,具体使用方式到反射里再看。
反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。其实反射并不是 Java 独有的,许多编程语言都提供了反射功能。
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
通过反射机制,可以在运行时访问 Java 对象的属性,方法,构造方法等。
反射的主要应用场景有:
Java 中的 java.lang.reflect 包提供了反射功能 ,下面演示一下使用 reflect 包获取类的成员属性、静态属性、成员方法、类方法、构造方法的示例,为了演示反射,我们使用一直举例用的 Phone 类,为了演示先再给它添加一个类属性和方法。
- package com.example.factory;
-
- public class Phone extends Commodity {
-
- // 给Phone增加新的属性和方法
- private double screenSize;
- private double cpuHZ;
- private int memoryG;
- private int storageG;
- private String brand;
- private String os;
- private static int MAX_BUY_ONE_ORDER = 5;
-
- public Phone(
- String name, String id, int count, double soldPrice, double purchasePrice,
- double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
- ) {
- super(name, id, count, soldPrice * 1.2, purchasePrice);
- init(screenSize, cpuHZ, memoryG, storageG, brand, os);
- }
-
-
- public void init(double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os) {
- this.screenSize = screenSize;
- this.cpuHZ = cpuHZ;
- this.memoryG = memoryG;
- this.storageG = storageG;
- this.brand = brand;
- this.os = os;
- }
-
-
- public void describePhone() {
- System.out.println("此手机商品属性如下");
- describe();
- System.out.println("手机厂商为" + brand + ";系统为" + os + ";硬件配置如下:\n" +
- "屏幕:" + screenSize + "寸\n" +
- "cpu主频" + cpuHZ + " GHz\n" +
- "内存" + memoryG + "Gb\n" +
- "存储空间" + storageG + "Gb");
- }
-
- // 添加一个静态方法用于演示
- public static String getNameOf(Phone p){
- return p.getName();
- }
-
-
- public double buy(int count) {
-
- // TODO 这个方法里代码大部分和父类一样,肯定有方法解决
- if (count > MAX_BUY_ONE_ORDER) {
- System.out.println("购买失败,手机一次最多只能买" + MAX_BUY_ONE_ORDER + "个");
- return -2;
- }
- if (this.count < count) {
- System.out.println("购买失败,库存不够");
- return -1;
- }
- this.count -= count;
- double cost = count * soldPrice;
- System.out.println("购买成功,花费为" + cost);
- return cost;
- }
- ......
- }
上一节提到了 Java 里通过一个类的 Class 类实例,可以获取一个类所有的信息,获取 Class 类实例的方式我们这里就不再赘述了,直接开始演示反射。
获取类的成员属性和静态属性
Class 对象提供以下方法获取对象的成员(Field):
- package com.example;
-
- import com.example.factory.Phone;
-
- public class ReflectionTestApp {
- Phone phone = new Phone(
- "手机001","Phone001",100, 1999, 999,
- 4.5,3.5,4,128,"索尼","安卓"
- );
- //Class claz.getClass() 通过调用对象的getClass方法获取Class类的对象
- // 使用类.class的方式获取Class类对象
- Class claz = Phone.class;
-
- // 获取名为count的属性,该属性继承自父类
- Field countField = claz.getField("count");
- // 因为count是成员变量,用Filed.get()方法获取值的时候,要把具体对象传给方法
- System.out.println("通过反射获取count的值:"+countField.get(phone));
- // 设置count成员变量的值
- countFiled.set(phone, 100);
- // getField方法也能获取类的静态变量
- Field field = claz.getField("MAX_BUY_ONE_ORDER");
- // 获取属性值的时候,因为类静态变量不属于某个实例,所以给Field.get方法传递null参数
- System.out.println(field.get(null));
-
- // getFields 方法获取类的所有属性
- for (Field field : claz.getFields()) {
- System.out.println(field.getType() + " " + field.getName());
- }
-
- // getField能获取到本类及其所有父类的public的属性,但是非public的属性获取不到。
- // getDeclaredField方法,获取类声明的属性,不管是不是public的,都能获取到,但是只能获得本类
- // 声明的属性,不能获得父类的属性。比如
- // Field countField = clazz.getDeclaredField("count"); 是获取不到的
- // 获取声明的属性 memoryG
- Field countField = clazz.getDeclaredField("memoryG");
- // 把它的访问控制改成public的 ......
- countField.setAccessible(true);
- }
用反射获取和调用类的方法
Class 对象提供以下方法获取对象的方法(Method):
获取一个 Method 对象后,可以用 invoke 方法来调用这个方法,可以通过Method对象的 setAccessible 方法设置方法的访问控制。
- package com.example;
-
- import com.example.factory.Phone;
-
- public class ReflectionTestApp {
- Phone phone = new Phone(
- "手机001","Phone001",100, 1999, 999,
- 4.5,3.5,4,128,"索尼","安卓"
- );
- //Class claz.getClass() 通过调用对象的getClass方法获取Class类的对象
- // 使用类.class的方式获取Class类对象
- Class claz = Phone.class;
-
- // getMethod和getField方法一样能获取本类及其父类所有public方法
- Method buyMethod = clazz.getMethod("buy", int.class);
- // 获取一个 Method 对象后,可以用 invoke 方法来调用这个方法。
- System.out.println(buyMethod.invoke(phone, 10));
- // getDeclaredMethod和getDeclaredField效果一样,只能获取本类声明的方法
- Method descMethod = clazz.getDeclaredMethod("describePhone");
- // 设置方法为public, 只是演示, describePhone本身就是public的
- descMethod.setAccessible(true);
- descMethod.invoke();
- // 获取本类定义的静态方法
- Method staticMethod = clazz.getMethod("getNameOf", Phone.class);
- // invoke方法返回的是Object对象,所有要把返回值类型转换一下才能赋值给String变量
- String str = (String) staticMethod.invoke(null, phone);
- System.out.println(str);
- }
更多反射能力
反射还有很多能力,比如用Method对象可以继续反射出方法的参数列表,反射获取类的构造方法,通过Constructor对象创建实例等等
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。
枚举的典型应用场景:错误码、状态机等。
在枚举出现前,Java 里定义一些常量状态码都是用的静态属性,比如:
- public class ArticleState {
-
- public static final int Draft = 1; //草稿
-
- public static final int Published = 2; //发布
-
- public static final int Deleted = 3; // 已删除
- }
这种用法,肯定是比在程序里直接用魔术数字要强100倍,但是也有缺点,比如不能更好做类型限制
- public Boolean checkArticleState(int state) {
-
- ...
-
- }
上面这个方法用来检查文章状态,本意是让调用者传入 ArticleState 的三个静态常量之一,但由于没有类型上的约束,因此传入任意一个 int 值在语法上也是允许的,编译器也不会提出任何警告。
类似上面这种情况就可以用把形参类型设置成枚举类型去约束,比如
- public enum ArticleState {
- Draft, Published, Published
- }
-
- public Boolean checkArticleState(ArticleState state) {
-
- ...
-
- }
枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显式继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。
使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;
枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final 修饰,无须程序员显式添加。
我们可以在枚举里添加自定义属性,相应枚举的构造函数也要设置这些属性,同理也可以给枚举添加自定义的方法。
- package com.example.factory;
-
- // 使用enum而非class声明
- public enum Category {
-
- // 必须在开始的时候以这种形式,创建所有的枚举对象
- FOOD(1, "食品"),
- COOK(3, "烹饪"),
- SNACK(5, "零食"),
- CLOTHES(7, "衣服"),
- ELECTRIC(9, "电子产品");
-
- // 可以自定义属性
- private int id;
-
- private String name;
-
- // 构造方法必须是private的,不写也是private的
- Category(int id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public int getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
-
- @Override
- public String toString() {
- return "Category{" +
- "id=" + id + " name=" + name +
- "}";
- }
-
- }
在枚举中可以覆盖一些枚举父类 java.lang.Enum 中的方法用于实现自己的业务,比如上面覆盖了 toString() 方法。
在 enum 中,提供了一些基本方法:
- package com.example;
-
- import com.example.factory.Category;
-
- public class EnumTest {
- public static void main(String[] args) {
- // 获取所有枚举,看看枚举实例有哪些方法
- for (Category category : Category.values()) {
- System.out.println("-----------" + category.getId() + "------------");
- System.out.println(category.ordinal());
- System.out.println(category.name());
- System.out.println(category.toString());
- }
- }
- }
-
-
-
- // 以下是输出
- -----------1------------
- 0
- FOOD
- Category{id=1 name=食品}
- -----------3------------
- 1
- COOK
- Category{id=3 name=烹饪}
- -----------5------------
- ...... // 省略更多
接口的定义使用interface,而非class。接口中的方法,就是这个类型的规范,接口专注于规范,定义标准,怎么实现这些标准不是接口关心的,而是实现这个接口的类关心的。
接口无法被实例化,也就是不可以 new 一个接口的实例。接口里的方法都是 public abstract 修饰的公共抽象方法,方法有名字,参数和返回值,没有方法体,以分号";"结束。建议在定义接口里的方法时,把方法的注释写好,这样类实现的时候更好理解应该怎么去实现。
注:Java8 以后接口里也可以定义静态方法、私有方法和带有默认实现的方法,不过感觉没啥用,这部分知道就行,不再进行举例。
- package com.example.factory.interface;
-
- import java.util.Date;
-
- public interface ExpireDateCommodity {
-
- /**
- * 截止到当前,商品的保质期天数是否超过传递的天数
- *
- * @param days 截止到当前,保质期超过这么多天
- * @return 截止到当前,true如果保质期剩余天数比参数长,false如果保质期不到这多天
- */
- boolean notExpireInDays(int days);
-
- /**
- * @return 商品生产日期
- */
- Date getProducedDate();
-
- /**
- * @return 商品保质期到期日
- */
- public abstract Date getExpireDate();
-
- /**
- * @return 截止到当前,剩余保质期还剩下总保质期长度的百分比
- */
- double leftDatePercentage();
-
-
- /**
- * 根据剩余的有效期百分比,得出商品现在实际的价值
- * @param leftDatePercentage 剩余有效期百分比
- * @return 剩余的实际价值
- */
- double actualValueNow(double leftDatePercentage);
-
-
- public static final int VAL_IN_INTERFACE = 999;
-
- }
接口里不能定义局部变量,定义的变量默认都是public static final的,这三个修饰符同样可以省略。接口甚至可以不定义任何方法,只用来规定一种接口类型。
- public interface VirtualCommodity {
- }
Java 的接口还可以继承,Java 的类是单继承的,但是接口却可以是多继承。
- //子接口
- public interface C extends A,B {
- //内容省略
- }
一个类实现某个接口的标准是,类从接口继承了所有抽象方法。非抽象类,必须实现所有接口里定义的抽象方法。一个类只能继承一个父类,但是可以实现多个接口。
- package com.example;
-
- import java.util.Date;
-
- import com.example.factory.interface.*;
-
- public class GymCard extends Commodity implements ExpireDateCommodity, VirtualCommodity {
-
- private Date produceDate;
- private Date expirationDate;
-
- public GamePointCard(String name, String id, int count, double soldPrice, double purchasePrice, Date produceDate, Date expirationDate) {
- super(name, id, count, soldPrice, purchasePrice);
- this.produceDate = produceDate;
- this.expirationDate = expirationDate;
- }
-
- public boolean notExpireInDays(int days) {
- return daysBeforeExpire() > days;
- }
-
- public Date getProducedDate() {
- return produceDate;
- }
-
- public Date getExpireDate() {
- return expirationDate;
- }
-
- public double leftDatePercentage() {
- return 1.0 * daysBeforeExpire() / (daysBeforeExpire() + daysAfterProduce());
- }
-
- public double actualValueNow(double leftDatePercentage) {
- return soldPrice;
- }
-
- private long daysBeforeExpire() {
- long expireMS = expirationDate.getTime();
- long left = expireMS - System.currentTimeMillis();
- if (left < 0) {
- return -1;
- }
- // 返回值是long,是根据left的类型决定的
- return left / (24 * 3600 * 1000);
- }
-
- private long daysAfterProduce() {
- long expireMS = expirationDate.getTime();
- long left = System.currentTimeMillis() - expireMS;
- if (left < 0) {
- return -1;
- }
- // 返回值是long,是根据left的类型决定的
- return left / (24 * 3600 * 1000);
- }
-
-
- }
如果我们又有一个类跟 GymCard 类一样实现了 ExpireDateCommodity 接口,那是不是又得跟 GymCard 类一样把所有与出厂和过期日期相关的方法实现一遍呢,或者说把 GymCard 这些方法拷贝到新要定义的类里呢?这么干听着就很傻,那怎么解决这个问题呢,这就需要----抽象类来发挥作用了。
抽象类用 abstract 修饰,抽象类可以继承别的普通类或者抽象类,也可以实现接口。 抽象类可以有抽象方法,抽象方法可以来自实现的接口,也可以自己定义。比如实现上面 ExpireDateCommodity 接口的抽象类可以只实现其中的四个方法,剩下一个计算剩余价值的 actualValueNow 方法留给具体的子类来实现 这算是一个比较经典的用法,能让不同的商品子类灵活地设置商品折旧算法。
当然抽象类也可以没有抽象方法,所有的抽象类都不可以被实例化。 怎么理解没有抽象方法也能是抽象类呢?抽象类主要是表明这个类就是用来被继承的。
另外为了便于理解可以记住如果一个类存在至少一个抽象方法,那么这个类就必须声明成抽象类,声明的抽象类里可以不包含抽象方法。
简单来说,抽象类就两点特殊:1)被abstract修饰,可以有抽象方法 2)只能被继承,不可以被实例化。
- package com.example;
-
- import com.example.factory.interface.*;
-
- public abstract class AbstractExpireDateCommodity extends Commodity implements ExpireDateCommodity {
-
- private Date produceDate;
- private Date expirationDate;
-
- // 抽象类里构造方法的语法和类一样。
- public AbstractExpireDateMerchandise(String name, String id, int count, double soldPrice, double purchasePrice, Date produceDate, Date expirationDate) {
- super(name, id, count, soldPrice, purchasePrice);
- this.produceDate = produceDate;
- this.expirationDate = expirationDate;
- }
-
- public AbstractExpireDateMerchandise(String name, String id, int count, double soldPrice, Date produceDate, Date expirationDate) {
- super(name, id, count, soldPrice);
- this.produceDate = produceDate;
- this.expirationDate = expirationDate;
- }
-
- public AbstractExpireDateMerchandise(Date produceDate, Date expirationDate) {
- this.produceDate = produceDate;
- this.expirationDate = expirationDate;
- }
-
- // @ 是Java中的注解(annotation),后面我们会详细讲述
- // @Override代表此方法覆盖了父类的方法/实现了继承的接口的方法,否则会报错
- public boolean notExpireInDays(int days) {
- return daysBeforeExpire() > 0;
- }
-
- public Date getProducedDate() {
- return produceDate;
- }
-
- public Date getExpireDate() {
- return expirationDate;
- }
-
- public double leftDatePercentage() {
- return 1.0 * daysBeforeExpire() / (daysBeforeExpire() + daysAfterProduce());
- }
-
- // 这里不实现接口的这个方法,留给子类去实现,
- // @Override
- // public double actualValueNow(double leftDatePercentage) {
- // return 0;
- // }
-
- // 抽象类里自己定义的抽象方法,可以是protected,也可以是缺省的,这点和接口不一样
- // protected abstract void test();
-
-
- // 这俩方法是私有的,返回值以后即使改成int,也没有顾忌
- private long daysBeforeExpire() {
- long expireMS = expirationDate.getTime();
- long left = expireMS - System.currentTimeMillis();
- if (left < 0) {
- return -1;
- }
- // 返回值是long,是根据left的类型决定的
- return left / (24 * 3600 * 1000);
- }
-
- private long daysAfterProduce() {
- long produceMS = produceDate.getTime();
- long past = System.currentTimeMillis() - produceMS;
- if (past < 0) {
- // 生产日期是未来的一个时间?315电话赶紧打起来。
- return -1;
- }
- // 返回值是long,是根据left的类型决定的
- return past / (24 * 3600 * 1000);
- }
- }
使用抽象类
- package com.example;
-
- import java.util.Date;
-
- import com.example.factory.interface.*;
-
- // 一个类只能继承一个父类,即使是抽象类,也只能是一个
- public class GameCard extends AbstractExpireDateCommodity {
-
- public GamePointCard(String name, String id, int count, double soldPrice, double purchasePrice, Date produceDate, Date expirationDate) {
- super(name, id, count, soldPrice, purchasePrice, produceDate, expirationDate);
- }
-
- /**
- * 游戏充值点卡,过期前都是保值的
- */
- @Override
- public double actualValueNow(double leftDatePercentage) {
- return super.getSoldPrice();
- }
-
- }
-
-
- -------------------------
-
- public class UseAbsClass {
-
- public static void main(String[] args) {
-
- Date produceDate = new Date();
- Date expireDate = new Date(produceDate.getTime() + 365L * 24 * 3600 * 1000);
- GameCard gameCard = new GameCard(
- "点卡001", "点卡001", 100, 1999, 999,
- produceDate, expireDate
- );
-
- // 父类的引用可以用子类的引用赋值,抽象类也一样
- AbstractExpireDateCommodity am = gameCard;
-
- am.describe();
-
- }
- }
在Java中,将一个类的定义放在另一个类的定义内部,这就是内部类。内部类本身就是外部类的一个属性。
- public class Outer {
- private int i = 1;
- private static int k = 2;
-
- public Outer(){
- }
-
- // 定义内部类
- class inner{
- public void visitOuter(){
- System.out.println("visit"+i);
- System.out.println("visit"+k);
- }
- }
- }
内部类按种类划分总共可以分为四种:静态内部类,成员内部类、局部内部类、匿名内部类。匿名内部类就是我们经常说的匿名类,使用的最多,其他三种使用起来没有匿名类那么频繁。
定义在类内部的,由 static 修饰的静态类,就是静态内部类。
- public class Outer {
- private int i = 1;
- private static int k = 2;
-
- public static void outerStaticFunc(){
- // 外部静态方法
- }
- public void outerNonStaticFunc(){
- // 外部成员方法
- }
-
- static class StaticInner{
- static int innerI = 3;
- int innerJ = 4;
- static void innerFunc(){
- System.out.println();
- outerStaticFunc();
- // outerNonStaticFunc(); 报错,静态内部类不能访问外部类的非static方法
- }
- public void visit(){
- // System.out.println("visit" + i); 报错,不能访问外部类的非static属性
- System.out.println("visit" + k);
- }
- static public void staticVisit(){
-
- }
- }
-
- // 外部类访问内部类静态成员
- public void outerAccessInner(){
- // 外部类访问静态内部类【静态成员|方法 内部类.静态成员|方法】
- System.out.println(StaticInner.innerI);
- StaticInner.staticVisit();
- // 外部类访问静态内部类【非静态成员|方法】 实例化内部类即可
- StaticInner staticInner = new StaticInner();
- int innerJ = staticInner.innerJ;
- staticInner.visit();
- }
- }
静态内部类,是在类中使用static修饰的内嵌类,可以有访问控制符。静态内部类和静态方法,静态变量一样,都是类的静态组成部分。
静态内部类也是类,在继承,实现接口方面,都是一样的。
使用静态内部类
- public class UseStaticInnerClass {
- public static void main(String[] args) {
- Outer.StaticInner staticInner = new Outer.StaticInner();
- staticInner.visit();
- }
- }
静态内部类的总结
定义在类内部,成员位置上的非静态类,就是成员内部类。
- public class Outer {
- private int i = 1;
- private static int k = 2;
-
- public static void outerStaticFunc(){
-
- }
-
- public void outerNonStaticFunc(){
-
- }
-
- class Inner{
- // static int innerI = 3; 报错 内部类中不允许定义静态变量
-
- // 内部类的成员变量可以与外部类的重名
- int i = 3;
- int innerJ = 4;
- void innerFunc1(){
- // 外部类变量与内部类变量没有同名,在成员内部类可以直接用变量名访问外部类变量
- System.out.println(k);
- // 内部类中访问自己的变量直接用变量名
- System.out.println(i);
- // 访问外部类与内部类重名的成员变量 this.变量名
- System.out.println(this.i);
- }
-
- public void visit(){
- System.out.println("visit"+i);
- System.out.println("visit"+k);
- }
- }
-
- // 外部类非静态方法访问成员内部类
- public void outerAccessInner1(){
- Inner inner = new Inner();
- inner.innerFunc1();
- }
- }
在外部,创建成员内部类的方式
- public class UseInnerClass {
- public static void main(String[] args) {
- // 1.建立外部类对象
- Outer outer = new Outer();
- // 2.根据外部类对象建立内部类对象
- Inner inner = outer.new Inner();
- // 3.访问内部类的方法
- inner.visit();
- }
- }
成员内部类的使用总结:
成员内部类的优点:用内部类定义在外部类中不可访问的属性。这样在外部类中实现了比外部类的private还要小的访问权限。
注意:
定义在方法中的内部类,就是局部内部类。
- public class Outer {
- private int i = 1;
- private static int k = 2;
-
- public void func(int q){
- final int i = 3;
- final int z = 4;
- // 局部内部类,定义在方法内部, 不能用访问控制符修饰
- class Inner{
- int i = 300; // 可以定义与外部类同名的变量
- // static int m = 30; 报错, 不可以定义静态变量
- // 局部内部类的构造方法
- Inner(int k){
- // 演示用, 什么也不做
- }
- int inner = 100;
- void innerFunc(int f){
- // 内部类没有与外部类同名的变量, 可在内部类直接访问外部类实例变量
- System.out.println(k);
- // 内部类与外部类变量名相同, 访问内部变量 this.内部变量
- System.out.println(this.i);
- // 变量名相同, 访问外部类的变量,使用 外部类类名.this.变量名
- System.out.println(Outer.this.i);
- }
- }
- // 创建局部内部类
- Inner inner = new Inner(k);
- inner.innerFunc(100);
- }
-
- public static void static_func(int y){
- int d = 3;
- class Inner{
- private void func(){
- // System.out.println(i); 编译错误, 定义在静态方法中的局部类不可以访问外部类的成员变量
- // 可以访问静态类变量
- System.out.println(k);
- }
- }
- Inner inner = new Inner();
- inner.func();
- }
- }
总结
所有内部类里,匿名类是使用最多的,一般的时候在需要某个抽象类或者接口的实例时,用匿名类即时实现接口并直接使用,这样免去了定义类的部分繁琐细节。
匿名类是用来创建接口或者抽象类的实例的。比如有下面的接口和抽象类:
- package com.example.factory;
-
- public interface UnitSpecI {
- double getNumSpec();
-
- String getProducer();
- }
-
- ------
-
- package com.example.factory;
-
- public abstract class UnitSpecAbs {
-
- private double spec;
-
- private String producerStr;
-
- public UnitSpecAbs(double spec, String producer) {
- this.spec = spec;
- this.producerStr = producer;
- }
-
- public abstract double getNumSpec();
-
- public abstract String getProducer();
-
- public double getSpec() {
- return spec;
- }
-
- public String getProducerStr() {
- return this.producerStr;
- }
- }
接下来在我们一直用的Phone类里演示下匿名类的创建和使用。
通过下面的代码感受一下,匿名类可以初始现在任何有代码的地方这句话
- package com.example.factory;
-
- public class Phone extends Commodity {
-
- private double screenSize;
- private UnitSpecI cpu;
- private int memoryG;
- private int storageG;
- private String brand;
- private String os;
- private double speed;
-
- // 匿名类的语法如下,new后面跟着一个接口或者抽象类
- private UnitSpecI anywhere = new UnitSpecI() {
- @Override
- public double getNumSpec() {
- return Phone.this.speed;
- }
-
- @Override
- public String getProducer() {
- return "Here";
- }
- };
-
- // 对于抽象类的匿名类,也可以给构造方法传递参数
- private UnitSpecAbs anywhereAbs = new UnitSpecAbs(1.2, "default") {
- @Override
- public double getNumSpec() {
- return Math.max(Phone.this.speed, this.getSpec());
- }
-
- @Override
- public String getProducer() {
- return this.getProducerStr();
- }
- };
-
-
- public Phone(
- String name, String id, int count, double soldPrice, double purchasePrice,
- double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os
- ) {
-
- double localCPUHZ = cpuHZ;
-
- localCPUHZ = Math.random();
-
- this.screenSize = screenSize;
- // 可以像平常的类一样使用局部内部类
- this.speed = cpuHZ;
- this.cpu = new UnitSpec() {
- @Override
- public double getNumSpec() {
- // 实际用的比较多的是匿名类和静态内部类(为了单例),成员内部类和局部内部类用的比较少。
- // 方法里的匿名类在访问局部变量和参数时,它们也必须是实际final的
- return Math.max(Phone.this.speed, Math.max(cpuHZ, localCPUHZ));
- }
-
- @Override
- public String getProducer() {
- return "Anonymous";
- }
- };
- this.memoryG = memoryG;
- this.storageG = storageG;
- this.brand = brand;
- this.os = os;
-
- this.setName(name);
- this.setId(id);
- this.setCount(count);
- this.setSoldPrice(soldPrice);
- this.setPurchasePrice(purchasePrice);
- }
-
-
- public UnitSpecI getCpu() {
- return cpu;
- }
-
- public void setCpu(UnitSpecI cpu) {
- this.cpu = cpu;
- }
-
- }
- package com.exmpale;
-
-
- import com.example.factory.Phone;
- import com.example.factory.UnitSpecI;
-
- public class UseAnonymousClass {
- public static void main(String[] args) {
- Phone phone = new Phone(
- "手机001", "Phone001", 100, 1999, 999,
- 4.5, 3.5, 4, 128, "索尼", "安卓"
- );
-
- printSpec(phone.getCpu());
-
- // 匿名类的实例作为参数传递也没问题
- phone.setCpu(new UnitSpec() {
- @Override
- public double getNumSpec() {
- return 123;
- }
-
- @Override
- public String getProducer() {
- return "HuaWei";
- }
- });
- }
- }
总结一下匿名内部类
不过在 Java8 引入 Lambda 后,大部分使用匿名类的场景也可以使用 Lambda,写起来也更简洁,但是可读性不如匿名类好,关于 Lambda 的内容我们放在后面单独章节进行讲解。