ZKX's LAB

跟光磊学Java开发-面向对象编程进阶

2020-12-15新闻17

接口接口概述

接口和类一样,也是一种引用数据类型,接口主要是用来定义常量和方法。

JDK7之前接口中可以定义常量和抽象方法

JDK8以后接口中可以定义默认方法和静态方法

JDK9以后可以定义私有方法接口的定义和实现

定义类使用class关键字,而定义接口使用interface,接口编译后也会产生对应的class文件。

接口不能通过new关键字来创建对象,需要使用类来实现接口,类实现接口的关键字是implements。package net.ittimeline.java.core.jdk.oop.interfaces;/** * 接口及其成员的定义 * @author tony 18601767221@163.com * @version 2020/12/14 20:18 * @since JDK11 */public interface UserService { /** 接口中的成员变量默认是public static final ,这三个修饰符可以省略不写*/ public static final int DEFAULT_LOGIN_COUNT = 1; /** * 登录抽象方法 * 接口中的方法默认都是public abstract修饰的,public abstract可以省略 */ public abstract void login(); /** * 注册 * 该方法是默认方法 * 默认方法都是public default,default不能省略 */ public default void register(){ System.out.println("user default register()"); } /** * 获取登录次数 * 该方法是静态方法 * @return */ public static int getDefaultLoginCount(){ getUserInfo(); return DEFAULT_LOGIN_COUNT; } /** * 获取用户信息 * 该方法是私有静态方法 */ private static void getUserInfo(){ } /** * 找回密码 * 该方法是默认方法 */ default void findPassword(){ getEmail(); } /** * 获取邮箱 * 该方法是一个非静态的私有方法 */ private void getEmail(){ }}

因为接口不能创建对象,因此需要定义类来实现接口,一个类可以实现1个接口或者多个接口,当实现多个接口时,接口使用逗号分隔。此外一个类在继承父类的情况下还可以实现多个接口。但是要先继承再实现。

例如java.util.ArrayList类先继承了父类AbstractList后再实现了List、RandomAccess,Cloneable,Serializable四个接口。

我们也可以定义一个子类可以继承单个父类,实现多个接口。

类图如下

首先定义一个父类CommonUserService,该类中包含两个通用方法:doRegister()和doLogin()package net.ittimeline.java.core.jdk.oop.interfaces;/** * 通用用户服务 * * @author tony 18601767221@163.com * @version 2020/12/14 20:36 * @since JDK11 */public class CommonUserService { protected void doRegister(){ System.out.println("执行注册"); } protected void doLogin(){ System.out.println("执行登录"); }}

然后再定义一个供子类UserServiceImpl实现的接口LogoutService,该接口中包含一个注销用户的方法package net.ittimeline.java.core.jdk.oop.interfaces;/** * 注销用户服务接口 * * @author tony 18601767221@163.com * @version 2020/12/14 20:38 * @since JDK11 */public interface LogoutService { /** * 注销用户 */ void logout();}

在子类UserServiceImpl中继承CommonUserService父类并且实现UserService和LogoutService两个接口package net.ittimeline.java.core.jdk.oop.interfaces.impl;import net.ittimeline.java.core.jdk.oop.interfaces.CommonUserService;import net.ittimeline.java.core.jdk.oop.interfaces.LogoutService;import net.ittimeline.java.core.jdk.oop.interfaces.UserService;/** * 接口的实现 * * @author tony 18601767221@163.com * @version 2020/12/14 20:35 * @since JDK11 */public class UserServiceImpl extends CommonUserService implements UserService, LogoutService { @Override public void login() { super.doLogin(); } @Override public void register() { super.doRegister(); } @Override public void logout() { System.out.println("用户服务注销用户"); }}子类实现单个接口时成员的访问特点

接口中的抽象方法默认是使用public abstrat修饰,实现接口的普通子类必须重写接口中的所有抽象方法。

接口中的静态方法是使用public static修饰,public可以省略,但是static不能省略。可以直接使用接口名直接访问静态方法,但是无法通过子类对象访问静态方法。

接口中的私有方法是使用private修饰,私有方法也可以是静态的。私有方法只能在接口中调用,例如私有非静态方法在默认方法中调用,私有静态方法可以在接口的静态方法或者默认方法中调用。/** * 获取登录次数 * 该方法是静态方法 * @return */ public static int getDefaultLoginCount(){ getUserInfo(); return DEFAULT_LOGIN_COUNT; } /** * 获取用户信息 * 该方法是私有静态方法 */ private static void getUserInfo(){ } /** * 找回密码 * 该方法是默认方法 */ default void findPassword(){ getEmail(); } /** * 获取邮箱 * 该方法是一个非静态的私有方法 */ private void getEmail(){ }

接口中的常量默认是使用public static final修饰,也就是静态常量,必须而且只初始化一次。可以使用接口名.常量名访问,也可以使用实现类对象访问接口的常量值。

接口中的默认方法是使用public default,public可以省略,但是default不能省略。使用实现类对象调用接口中的默认方法,实现类也可以重写接口的默认方法。package net.ittimeline.java.core.jdk.oop.interfaces;import net.ittimeline.java.core.jdk.oop.interfaces.impl.UserServiceImpl;/** * 接口成员的访问特点 * * @author tony 18601767221@163.com * @version 2020/12/14 20:56 * @since JDK11 */public class InterfaceMemberTest { public static void main(String[] args) { // 接口成员中的常量可以直接通过接口名.常量名访问 System.out.println("UserService.DEFAULT_LOGIN_COUNT = "+UserService.DEFAULT_LOGIN_COUNT); //也可以使用实现类.常量名访问 System.out.println("UserServiceImpl.DEFAULT_LOGIN_COUNT = "+ UserServiceImpl.DEFAULT_LOGIN_COUNT); //接口的抽象方法必须要被实现类重写 //接口中的默认方法可以由实现类的对象调用或者重写 //实现类对象调用接口的默认方法 UserService userService=new UserServiceImpl(); userService.register(); //接口中的静态方法只能直接调由接口调用,但是不能由实现类的对象调用 UserService.getDefaultLoginCount(); }}子类实现多个接口时成员的访问特点

以java.util.ArrayList为例,在它的类体系结构中,ArrayList类实现了RandomAccess,Seriazable,Cloneable和List四个接口,这也就是子类多实现接口。

当一个类实现多个接口时如果多个接口中同时存在相同的常量或者方法时可能会存在几种冲突情况

常量:当多个父接口中有相同的常量,实现类不会继承,也就不能访问

抽象方法:如果多个父接口中有相同的抽象方法,实现类重写一个抽象方法即可。

默认方法:因为默认方法可以被实现类继承,因此多个父接口中存在相同的默认方法,实现类必须重写一次默认方法。重写的方法不需要加default关键字

静态方法:静态方法只会提供给接口直接调用,所以对实现类无影响

私有方法:由于私有方法只能在接口中直接调用,所以对实现类无影响接口多继承时成员的访问特点类和类之间的关系是可以单继承和多层继承,Java的类不支持多继承

接口和接口之间可以单继承、多继承和多层继承,接口的继承也是使用extends关键字

单继承:一个接口继承一个接口

多继承:一个接口同时继承多个接口

多层继承:子类接口继承父类接口,父类接口继承爷爷类接口

以java.util.ArrayList为例,在它的类体系结构中,Collection接口就继承了Iterable接口,这就是接口的单继承,而List接口继承了Collection接口,Colletion接口继承了Iteable接口,这就是接口的多层继承。

接口的多继承在项目开发时几乎用不到。

当一个接口同时继承多个父类接口时,多个父类接口如果存在相同的常量或者方法时可能会存在几种冲突情况

常量:当多个父接口中有相同的常量,子类接口不会继承,也无法访问,直接使用父接口访问父类的常量即可,因为接口中的常量是给接口直接使用

抽象方法:如果多个父接口中有相同的抽象方法,子接口会继承一个。

默认方法:因为默认方法可以被实现类继承,因此多个父接口中存在相同的默认方法,子接口必须重写一次默认方法。重写的方法需要加default关键字,因为接口的默认方法是public default。

静态方法:如果多个父接口中有多个相同的静态方法,子类接口不会继承,也无法访问

私有方法:由于私有方法只能在接口中直接调用,所以对子接口无影响实现类继承父类又实现接口的访问特点

常量:当父类和多个父接口中有相同的常量,实现类不会继承,也无法访问

抽象方法:如果父类和多个父接口中有相同的抽象方法,实现类必须重写一次

默认方法:如果父类和多个父接口中有相同的默认方法,实现类会优先调用父类的默认方法,如果父类没有默认方法,会使用接口的默认方法。

静态方法:如果父类和多个父接口中有多个相同的静态方法,实现类只会调用父类的静态方法,因为接口的静态方法不能被实现类继承。

私有方法:由于私有方法只能在父类和父接口中直接调用,所以对实现类无影响多态多态的定义和实现

多态(Polymorphism) 是继封装、继承之后的面对象的第三大特性.多态指的是对于同一种行为,通过不同的事物,可以体现出来不同的形态。映射到Java中的多态就是指的同一个方法,对于不同的子类对象有不同的实现。多态有三种表现形式:普通父类多态、抽象父类多态和父接口多态。

实现多态要满足3个前提条件

首先要有继承或者实现关系,继承就是子类继承父类,实现就是子类实现父接口

父类的引用指向子类的对象,例如之前使用 AbstractOrderTemplate jingdong =new JingDongOrderTemplate();

方法的重写,例如在JingDongOrderTemplate、TaobaoOrderTeamplate,PinDuoDuoOrderTemplate重写了父类AbstractOrderTemplate的抽象方法,通过子类重写父类的方法,实现同一个方法对于不同的子类对象有不同的实现。这样多态才有意义。

这里再举一个例子:动物吃饭的多态,即抽象父类多态

首先定义一个抽象父类Animal,该类种包含一个抽象方法eat()package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 动物类 * * @author tony 18601767221@163.com * @version 2020/12/14 9:43 * @since JDK11 */public abstract class Animal { /** * 吃饭 */ public abstract void eat();}

然后定义子类Dog和Cat去重写父类Animal的eat()方法

Dog子类,子类有一个字节特有的lookHome()方法package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 狗类 * * @author tony 18601767221@163.com * @version 2020/12/14 9:43 * @since JDK11 */public class Dog extends Animal{ @Override public void eat() { System.out.println("小狗吃骨头"); } /** * 子类特有的方法看家 */ public void lookHome(){ System.out.println("小狗看家"); }}

Cat子类,子类有一个自己特有的catchMouse()方法package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 猫 * * @author tony 18601767221@163.com * @version 2020/12/14 9:44 * @since JDK11 */public class Cat extends Animal{ @Override public void eat() { System.out.println("小猫吃鱼"); } /** * 子类特有的方法 抓老鼠 */ public void catchMouse(){ System.out.println("小猫抓老鼠"); }}

抽象父类多态测试package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 抽象多态测试用例 * 实现多态的三个条件 * 1.存在继承或者实现关系 * 2.父类的引用指向子类的对象 * 3.存在方法的重写 * * @author tony 18601767221@163.com * @version 2020/12/14 9:44 * @since JDK11 */public class AbstractParentClassPolymorphismTest { public static void main(String[] args) { //父类的引用指向子类的对象 Animal dog=new Dog(); //调用父类引用的方法,实际上执行的是子类的方法,此时就发生了多态 dog.eat(); Animal cat =new Cat(); cat.eat(); }}

程序运行结果

多态时访问成员的特点

多态时访问成员指的是访问成员变量和成员方法的特点,多态时访问成员要考虑编译期和运行期两种情况。

多态时访问成员变量,编译时找父类对象的成员变量,运行时也是找父类对象的成员变量

多态时访问成员方法分成静态方法和非静态方法两种,多态时访问非静态方法,编译时找父类的非静态方法,运行时找子类的非静态方法。多态访问静态方法,编译时找父类的静态方法,运行也是找父类的静态方法。

因此总结多态时访问成员的特点就是除了非静态方法编译时看父类,运行时看子类,其他都是看父类。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态时访问成员变量的特点 * 此时使用普通父类实现多态 * @author tony 18601767221@163.com * @version 2020/12/14 22:54 * @since JDK11 */public class PolymorphismMemberTest { public static void main(String[] args) { Person person=new Employee(); // 多态时父类的引用无法访问子类的成员变量,只能访问父类的成员变量 //多态时使用父类的引用访问成员变量无论是编译时还是运行时找的都是父类的成员变量 System.out.println("多态时使用父类的引用访问成员变量 person.employeeNo = "+person.employeeNo); // 多态时父类的引用调用成员方法编译时找的是父类,运行时找的是子类 System.out.println("多态时父类的引用调用成员方法"); person.grow(); // 多态时父类的引用调用静态方法编译时找父类,运行时找父类 System.out.println("多态时父类的引用调用静态方法"); person.getPersonInfo(); }}class Person{ int employeeNo=10000; /** * 父类非静态成员方法 */ public void grow(){ System.out.println("Person grow()"); } /** * 父类静态成员方法 */ public static void getPersonInfo(){ System.out.println("Person getPersonInfo()"); }}class Employee extends Person{ int employeeNo=100001; /** * 子类非静态成员方法 */ @Override public void grow(){ System.out.println("Employee grow()"); } /** * 子类静态成员方法 */ public static void getPersonInfo(){ System.out.println("Employee getPersonInfo()"); }}

程序运行结果

多态的好处和弊端

多态的好处可以提高代码的扩展性,通过父类的引用指向子类的对象也就意味着父类类型的对象可以接收该父类一切子类类型的对象,其典型的应用场景就是定义方法时的形参可以使用父类对象,而在调用方法时的实参可以传递该父类所有子类的对象。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态带来了程序扩展性 * * @author tony 18601767221@163.com * @version 2020/12/14 23:16 * @since JDK11 */public class PolymorphismMethodExtendsTest { /** * eat方法 * @param animal 抽象父类作为形参,利用多态的特性,可以使用任意子类类型作为实参 */ public static void eat(Animal animal){ animal.eat(); } public static void main(String[] args) { //传入匿名子类对象作为eat()方法的参数 eat(new Dog()); eat(new Cat()); }}

程序运行结果

如果此时Animal类新增了一个子类Pandapackage net.ittimeline.java.core.jdk.oop.polymorphism;/** * 熊猫 * * @author tony 18601767221@163.com * @version 2020/12/14 23:19 * @since JDK11 */public class Panda extends Animal { @Override public void eat() { System.out.println("熊猫喝盆盆奶"); }}

而PolymorphismMethodExtendsTest类中的eat方法无需要做任何修改,只需要在调用eat()方法时传入一个新增的Panda()子类对象即可package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态带来了程序扩展性 * * @author tony 18601767221@163.com * @version 2020/12/14 23:16 * @since JDK11 */public class PolymorphismMethodExtendsTest { /** * eat方法 * @param animal 抽象父类作为形参,利用多态的特性,可以使用任意子类类型作为实参 */ public static void eat(Animal animal){ animal.eat(); } public static void main(String[] args) { //传入匿名子类对象作为eat()方法的参数 eat(new Dog()); eat(new Cat()); eat(new Panda()); }}

程序运行结果

但是存在着无法访问子类独有的方法,因为编译都无法通过,多态访问非静态成员方法时编译时看父类。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态的弊端 * 父类的引用无法访问子类的特有方法 * @author tony 18601767221@163.com * @version 2020/12/14 23:26 * @since JDK11 */public class PolymorphismWrongTest { public static void main(String[] args) { Animal dog =new Dog(); //dog.lookHome(); Animal cat =new Cat(); //cat.catchMouse(); }}

由于多态导致父类的变量无法访问子类独有的方法,因此才有了引用类型转换。

引用类型转换分成向上转型和向下转型两种

向上转型:子类类型自动转换成父类类型,这个过程是自动的。其语法格式为 父类类型 变量名=子类对象;

向下转型: 父类类型转换为子类类型,这个过程是强制的,其语法格式为子类类型 变量名 = (子类类型)父类变量;,类似于基本数据类型的强制类型转换。向下转型之间需要先向上转型,通过向下转型可以解决多态的弊端:父类的变量无法访问子类的方法。向下转型时有可能会出现类型转换异常(ClassCastException),因此在进行向下转型前需要使用instanceof运算符来进行类型判断,例如dog instanceof Dog,该运算符返回布尔类型的结果,如果是true则表表示变量指向的对象是属于后面的类型,否则变量指向的对象就不是后面的类型。出现类型转换的原因就是变量指向的对象和类型不匹配造成的。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 引用类型转型 * * @author tony 18601767221@163.com * @version 2020/12/14 23:34 * @since JDK11 */public class UpDownCasting { public static void main(String[] args) { //向上类型转换: 子类转父类 Animal dog=new Dog(); Animal cat= new Cat(); //向下转型:父类转子类 Dog dogDownCasting=(Dog)dog; Cat catDownCasting=(Cat) cat; //为了防止出现类型转换异常,转换之前使用instanceof运算符做类型判断 if(dog instanceof Dog){ dogDownCasting=(Dog)dog; } if(cat instanceof Cat){ catDownCasting=(Cat)cat; } }}多态的应用场景

多态的应用场景主要是变量多态,形参多态和返回值多态三个应用场景。其中最常见的是方法的形参多态和方法的返回值多态。变量多态 变量多态指的是同一个变量指向不同的对象,意义不大package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态的应用场景1:变量多态 * * @author tony 18601767221@163.com * @version 2020/12/14 11:09 * @since JDK11 */public class PolymorphismVariable { public static void main(String[] args) { //同一个变量可以同时指向多个对象 Animal animal=new Dog(); animal.eat(); animal=new Cat(); animal.eat(); }}

程序运行结果

形参多态 方法定义是的形参是父类类型的变量,调用子类方法时的实参可以是任意的子类类型的变量。 而如果调用子类特有的方法时,可以使用向下转型实现。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * * 多态的应用:作为方法的形参 * @author tony 18601767221@163.com * @version 2020/12/14 11:02 * @since JDK11 */public class PolymorphismMethodArgsTest { public static void eat(Animal animal){ //调用父类Animal对象的通用方法 animal.eat(); if(animal instanceof Dog){ Dog dog=(Dog)animal; dog.lookHome(); } if(animal instanceof Cat){ //向下转型 Cat cat =(Cat)animal; //调用子类的特有方法 cat.catchMouse(); } } public static void main(String[] args) { eat(new Dog()); eat(new Cat()); }}

程序运行结果

返回值多态一般返回值多态会结合形参多态使用。

返回值多态就是方法的返回值类型是父类类型。package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 多态的应用:返回值多态 * * @author tony 18601767221@163.com * @version 2020/12/14 11:07 * @since JDK11 */public class PolymorphismReturnValueTest { /** * 方法的返回值多态的前提是利用了方法的形参多态 * @param animal * @return */ static Animal getAnimal(Animal animal){ //业务逻辑处理,处理完了后返回处理后的结果 return animal; } public static void main(String[] args) { Animal animal = getAnimal(new Dog()); if(animal instanceof Dog){ Dog dog =(Dog) animal; dog.lookHome(); } animal=getAnimal(new Cat()); if(animal instanceof Cat){ Cat cat =(Cat) animal; cat.catchMouse(); } }}内部类内部类概述

假设在A类中定义了B类,那么B类就是A类的内部类,A类就是B类的外部类

内部类提供了更好的封装,避免外部类直接访问,不允许同一个包中的其他类直接访问。

内部类可以分为局部内部类、成员内部类和 匿名内部类 ,成员内部类又可以分为静态内部类和非静态内部类局本内部类

局部内部类是在方法中定义的,作用域仅限于方法,类似于局部变量,生命周期从属于方法,只能在方法中使用,局部内部类很少使用。package net.ittimeline.java.core.jdk.oop.innerclass;/** * 局部内部类 * * @author tony 18601767221@163.com * @version 2020/12/15 8:20 * @since JDK11 */public class LocalInnerClassTest { public static void showUserInfo(){ //这里无法使用,因为超过了UserInfo局部内部类的作用域 // UserInfo userInfo=new UserInfo(); } public static void main(String[] args) { /** * 局部内部类UserInfo * 此时只能在当前main方法中使用 * 类似于局部变量 */ class UserInfo{ private String userName; private String password; } UserInfo userInfo =new UserInfo(); userInfo.userName="tony"; userInfo.password="123456"; }}成员内部类

成员内部类作为外部类的一种成员出现,定义在类中,方法外。成员内部类中也可以定义成员变量和成员方法。

成员内部类分为非静态内部类和静态内部类非静态内部类 在其它类中访问非内部类的成员变量和成员方法,需要创建内部类的对象访问,因此如果有一个非静态内部类对象那么就一定存在对应的外部类对象,非静态内部类对象单独属于外部类的某个对象。 创建内部类对象的格式是 外部类名.内部类名 对象名 =new 外部类名().new 内部类名();

定义非静态外部类Body,包含一个非静态内部类Face,内部类默认是同包访问权限package net.ittimeline.java.core.jdk.oop.innerclass;/** * 身体 * 外部类 * @author tony 18601767221@163.com * @version 2020/12/15 8:27 * @since JDK11 */public class Body { /** * 脸 * 非静态内部类 */ class Face{ /** * 脸型 */ private String type; public String getType() { return type; } public void setType(String type) { this.type = type; } }}

在测试类中访问非静态内部类的成员,当前的测试类和外部类所在的内部类是位于同一个包net.ittimeline.java.core.jdk.oop.innerclass下。package net.ittimeline.java.core.jdk.oop.innerclass;/** * 内部类的访问特点 * * @author tony 18601767221@163.com * @version 2020/12/15 8:30 * @since JDK11 */public class NonStaticInnerTest { public static void main(String[] args) { // 在其他类中 需要创建内部类对象来访问内部类成员 //创建格式 外部类.内部类 对象名 = new 外部类().new()内部类(); Body.Face face = new Body().new Face(); face.setType("国字脸"); System.out.println(face.getType()); }}

程序运行结果

在外部类中访问非静态内部类的成员,需要创建内部类的对象访问。此时创建内部类的格式是内部类名 对象名 =new 内部类名();,但是外部类不能访问内部类的私有成员。

非静态内部类可以访问外部类的一切成员,包含私有成员,如果非静态内部类访问外部类的私有成员,可以使用外部类.this.后面跟上成员变量或者是成员方法即可。

非静态内部类不能包含静态方法和静态变量以及静态代码块。静态内部类可以看作外部类的一个静态成员,也就是使用static修饰的非静态内部类,静态内部类可以范围外部类的静态成员,不能访问外部类的普通成员。 在JDK的源码java.lang.Long类中就使用了静态内部类来维护一个缓存,不过它是一个私有的,因此只能在java.lang.Long类中使用。

静态内部类还有一个最佳实践:实现线程安全的单例模式

单例模式就是保证类只有一个对象实例

其实现方法是提供一个私有化的构造器,那样就不能在当前类以外的地方创建该对象的实例

然后提供一个静态的获取实例的方法,如果外部想要创建该类的对象,直接调用该静态方法即可。

内部类由于类加载机制,具有先天的线程安全性,因此是单例模式的最佳实现方式。package net.ittimeline.java.core.jdk.oop.innerclass;/** * 静态内部类实现单例模式 * * @author tony 18601767221@163.com * @version 2020/12/15 9:41 * @since JDK11 */public class StaticInnerClassSingleton { /** * 私有化构造器 */ private StaticInnerClassSingleton(){ } /** * 提供公有的获取对象实例的方法 * @return */ public static StaticInnerClassSingleton getInstance(){ //外部类成员可以访问静态内部类的私有成员 return StaticInnerClassSingletonHolder.INSTANCE; } /** * 在内部类中实例化外部类 */ private static class StaticInnerClassSingletonHolder{ private static final StaticInnerClassSingleton INSTANCE=new StaticInnerClassSingleton(); }}

创建一个单例模式测试类,多次调用获取对象实例的方法,查看对象的内存地址是一致。package net.ittimeline.java.core.jdk.oop.innerclass;/** * 静态内部类单例模式测试用例 * * @author tony 18601767221@163.com * @version 2020/12/15 10:22 * @since JDK11 */public class StaticInnerClassSingletonTest { public static void main(String[] args) { // 单例模式就是对象永远都只会有一个实例 StaticInnerClassSingleton staticInnerClassSingleton1 = StaticInnerClassSingleton.getInstance(); StaticInnerClassSingleton staticInnerClassSingleton2 = StaticInnerClassSingleton.getInstance(); System.out.println("staticInnerClassSingleton1 = " + staticInnerClassSingleton1); System.out.println("staticInnerClassSingleton2 = " + staticInnerClassSingleton2); }}

程序运行结果

匿名内部类

匿名内部类可以定义在类或者接口中,适合那种只需要使用一次的类,比如监听键盘等操作。

匿名内部类没有访问修饰符,没有构造方法。

匿名内部类是继承了父类的匿名子类对象

例如Dog继承了Animal父类,那么Dog类的对象就是继承了Animal类的子类对象,而如果是匿名内部类就是继承了父类Animal的匿名子类对象。

匿名内部类主要是用于简化代码。

例如如果是新增一个小动物类:Monkey继承自抽象父类Animal,重写eat()抽象方法。如果不使用匿名内部类,我们需要创建这样做

首先创建一个猴子子类继承自Animal父类,然后重写eat()方法package net.ittimeline.java.core.jdk.oop.polymorphism;/** * 猴子 * * @author tony 18601767221@163.com * @version 2020/12/15 9:23 * @since JDK11 */public class Monkey extends Animal{ @Override public void eat() { System.out.println("小猴吃香蕉"); }}

然后在测试类中创建Monkey对象,调用eat()方法package net.ittimeline.java.core.jdk.oop.innerclass;import net.ittimeline.java.core.jdk.oop.polymorphism.Animal;import net.ittimeline.java.core.jdk.oop.polymorphism.Monkey;/** * 没有匿名内部类实现抽象类的扩展 * * @author tony 18601767221@163.com * @version 2020/12/15 9:25 * @since JDK11 */public class NonAnonymousInnerClassTest { public static void main(String[] args) { // Animal animal=new Monkey(); animal.eat(); }}

程序运行结果

有了匿名内部类后,可以直接在main方法中创建Animal的子类对象,实现和刚才等价的效果。其区别是Animal类对象的作用域仅仅局限在main方法,出了main方法就无法使用了。package net.ittimeline.java.core.jdk.oop.innerclass;import net.ittimeline.java.core.jdk.oop.polymorphism.Animal;/** * 匿名内部类的使用 * 匿名内部类是继承了父类的匿名子类对象 * @author tony 18601767221@163.com * @version 2020/12/15 8:59 * @since JDK11 */public class AnonymousInnerClassTest { public static void main(String[] args) { /** =右边就是Animal的匿名内部类,其实就是Animal的子类对象,只是子类没有名字 */ Animal animal = new Animal() { @Override public void eat() { System.out.println("小猴吃香蕉"); } }; animal.eat(); }}

程序运行结果

匿名内部类是实现了父接口的匿名实现类对象

除了类可以创建匿名子类对象以外,接口按照同样的方式也可以创建匿名子类实现类package net.ittimeline.java.core.jdk.oop.innerclass;import net.ittimeline.java.core.jdk.oop.interfaces.UserService;/** * 匿名接口实现类 * 匿名内部类是实现了父接口的匿名实现类对象 * * @author tony 18601767221@163.com * @version 2020/12/15 9:14 * @since JDK11 */public class AnonymousInterfaceImplementsTest { public static void main(String[] args) { /** * 匿名内部类在接口中的使用 * =右边等价于创建UserService接口的实现类 */ UserService userService =new UserService() { @Override public void login() { System.out.println("我在登录"); } @Override public void getCurrentUser() { System.out.println("I'm tony"); } }; userService.login(); userService.getCurrentUser(); }}

程序运行结果

Java8以后支持了lambda表达式,相比匿名内部类可以实现更加简洁的语法,后续会详细介绍使用。

内部类时一个编译时的概念,一旦编译成功就会生成为两个完全不同的类,即包含内部类的类编译后会生成两个字节码文件。

非静态内部类编译后的字节码文件

编译目录:D:\workspace\java\ittimelinedotnet\java-core\java-core-jdk-oop\target\classes\net\ittimeline\java\core\jdk\oop\innerclass

测试代码 内部类的编译路径

对比之前的非静态内部类,匿名内部类生成的字节码在$后跟上了数字1,而非静态内部类是类名。

引用类型在项目开发中的使用

到目前为止已经学习的引用数据类型包含数组、类、抽象类、接口四种类型,不过后续项目开发中很少会使用数组。

那为什么还要学习数组呢?

因为数组是引用数据类型,学习数组可以了解JVM的内存模型,再学类、对象的内存模型时可以加深印象,因为引用类型的内存模型是类似的。

了解了数组,就可以了解JDK提供的ArrayList实现原理

引用数据类型(类、抽象类、接口)在日常开发中经常会作为成员变量, 方法的参数、返回值中使用。

引用数据类型在作为方法的参数、返回值时传递的都是地址值。

#技术编程

随机阅读

qrcode
访问手机版