- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
本文分享自华为云社区《【JAVA冷知识】JAVA居然支持多继承?让我们用内部类去吧!》,作者:山河已无恙 。
众多周知,对于面向对象语言来讲,JAVA是不支持多继承的,只支持单继承,但是提供了接口来补偿。
在实际的项目中,接口更多的用于行为的委托,把类本身一些是共性但又是特定的行为委托给一个接口的具体实现,当然接口也可以用于属性的委托,对象结构型的设计模式大都采用接口的方式来实现对对象内部组成的注册和操作
如果实现java的多继承,其实很简单,关键是对于内部类的特征的掌握,内部类可以继承一个与外部类无关的类,保证了内部类天然独立性,根据这个特性从而实现一个类可以继承多个类的效果
下面我们看一个Demo,声明父母两个接口,实现父母两个类,看如何通过内部类来继承父母类,而不是通过,接口委托的方式,
父亲接口
package com.liruilong;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description: 父亲接口
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog: https://liruilong.blog.csdn.net/
* @Date: 2022/2/12 2:48
*/
public interface Father {
/**
* @return: int
* @Description 强壮的行为
* @author LiRuilong
* @date 2022/2/12 2:49
**/
int strong();
}
父亲实现类
package com.liruilong;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description: 父亲类
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog: https://liruilong.blog.csdn.net/
* @Date: 2022/2/12 2:51
*/
public class FatherImpl implements Father {
static public String height = "身体超高";
/**
* @return: int
* @Description 强壮值
* @author LiRuilong
* @date 2022/2/12 2:51
**/
@Override
public int strong() {
return 8;
}
}
母亲接口
package com.liruilong;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description: 母亲接口
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog: https://liruilong.blog.csdn.net/
* @Date: 2022/2/12 2:50
*/
public interface Mother {
/**
* @return: int
* @Description 温柔的行为
* @author LiRuilong
* @date 2022/2/12 2:50
**/
int Kind();
}
母亲实现类
package com.liruilong;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description: 母亲类
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog: https://liruilong.blog.csdn.net/
* @Date: 2022/2/12 2:51
*/
public class MotherImpl implements Mother{
static public String pretty = "脸蛋特别漂亮";
/**
* @return: int
* @Description 温柔值
* @author LiRuilong
* @date 2022/2/12 2:51
**/
@Override
public int Kind() {
return 8;
}
}
OK,准备工作做好了, 看我们如何实现。
package com.liruilong;
import java.util.logging.Logger;
/**
* @Project_name: workspack
* @Package: com.liruilong
* @Description: 孩子类
* @Author: 1224965096@qq.com
* @WeChat_Official_Accounts: 山河已无恙
* @blog: https://liruilong.blog.csdn.net/
* @Date: 2022/2/12 13:16
*/
public class Son extends FatherImpl implements Mother {
static Logger logger = Logger.getAnonymousLogger();
MotherSpecial motherSpecial = new MotherSpecial();
@Override
public int strong() {
return super.strong() + 1;
}
@Override
public int Kind() {
return motherSpecial.Kind();
}
@Override
public String toString() {
return "Son{" +
"height=" + height +"," +
"pretty=" + MotherSpecial.pretty +
'}';
}
public class MotherSpecial extends MotherImpl {
@Override
public int Kind() {
return super.Kind() - 1;
}
}
public static void main(String[] args) {
Son son = new Son();
logger.info(son.toString());
logger.info(son.strong()+"");
logger.info(son.Kind()+"");
}
}
我们用内部类继承一个外部类无关的类,实现了Son
类的多继承
Bad level value for property: .level
Bad level value for property: java.util.logging.ConsoleHandler.level
Can''t set level for java.util.logging.ConsoleHandler
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: Son{height=身体超高,pretty=脸蛋特别漂亮}
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: 9
二月 12, 2022 2:02:06 下午 com.liruilong.Son main
信息: 7
Process finished with exit code 0
这里只是讨论这样的写法,我个人认为,这种方法有些鸡肋
。这种方式实现的多继承
,完全可以通组合
的方式来实现,我们简单分析一下优缺点
通过内部类的方式,把继承关系控制在类的内部,理论上比通过组合的方式更加安全
,代码可读性要好一点。
更符合设计原则中的迪米特法则
,又称最少知道原则(Demeter Principle),一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
首先通过继承的方式实现,打破了类的封装性
,子类依赖于其超类中特定功能的实现细节。 超类的实现有可能会随着发行版本的不同而有所变化,如果真的发生了变化,即使子类的代码完全没有改变,但是子类可能会遭到破坏因而,子类必须要跟着其超类的更新而演变,除非超类是专门为了扩展而设计的,并且具有很好的文挡说明
。
其次,通过这样的方式实现的,不符合常态思想,尤其内部类同名的情况,容易被忽略某些特性(见JDK源码)。
关于通过内部类来实现java多继承
的JDK
场景,我们简单分析一下
List<Integer> integers = Arrays.asList(1, 2, 3);
这个代码小伙伴们一定不陌生,这里通过Arrays
工具类来生成一个List
,但是这里的List
并不是真正的ArrayList
,而是在Arrays工具类
内部定义的一个继承了AbstractList的静态内部类ArrayList
,这里java通过内部类的方式巧妙的实现了。
.......
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
.................
但是这里同样需要注意的是通过内部类实现多继承要考虑其类的特殊性
:
这样生成的List
调用add
方法会抛不支持的操作的异常
,基于Arrays
的ArrayList
是一个静态私有内部类,除了Arrays能访问以外,其他类都不能访问,正常的ArrayList中add方法是ArrayList父类提供,Arrays的内部类ArrayList没有覆写add方法。
下面源码为ArrayList静态内部类
实现的个方法。
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
即没有实现add
和remove
方法,所以asList
返回的为一个长度不可变的列表
,数组为多长转换为列表为多长,即不在保持列表动态变长的特性
。
嗯,不多讲,直接上代码
ArrayList arrayList = new ArrayList();
LinkedList linkedList = new LinkedList();
Vector vector = new Vector();
linkedList.subList(2,3);
arrayList.subList(2,3);
vector.subList(2,3);
List
提供一个subList
方法,与String
的subString
有点类似,这里的List
通过subList
生成子list
方式也是通过内部类继承方式的多继承
实现的。
当然这里,具体需要分析,ArrayList
和其他List
的实现的方式略有不同
ArrayList
是自己定义的内部类SubList
继承AbstractList
实现的
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
.......
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
.....
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
.........
LinkedList
的subList
方法是由AbstractList
实现的,它会根据是不是随机存储
提供不同的实现方法
,subList
返回的类也是AbstractList的子类SubList
。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
........
public List<E> subList(int fromIndex, int toIndex) {
return (this instanceof RandomAccess ?
new RandomAccessSubList<>(this, fromIndex, toIndex) :
new SubList<>(this, fromIndex, toIndex));
}
class SubList<E> extends AbstractList<E> {
...
}
class RandomAccessSubList<E> extends SubList<E> implements RandomAccess{
.......
}
........
}
这里需要注意的是,不管是ArrayList还是LinkedList等其他List,通过SubList内部类生成的List,其所有的方法(get,add,set,remove等)都是在原始列表上操作
的,它自身并没有生成一个数组或是链表
,也就是子列表只是原列表的一个视图(View)
,所有的修改都反映在原列表
上。
单向链表 单向链表比顺序结构的线性表最大的好处就是不用保证存放的位置,它只需要用指针去指向下一个元素就能搞定。 单链表图解 图画的比较粗糙,简单的讲解一下: 上面四个长方形,每个长方
使用TCP,我正在设计一些类似于next的程序。 客户端在许多线程中的接收正在等待一台服务器的发送消息。但是,这是有条件的。 recv正在等待特定的发送消息。 例如 客户 thread 1: recv
我正在编写正则表达式来验证电子邮件。唯一让我困惑的是: 顶级域名可以使用单个字符吗?(例如:lockevn.c) 背景:我知道顶级域名可以是 2 个字符到任意字符(.uk、.us 到 .canon、.
是否可以在单个定义中定义同一 Controller 的多个路由? 例如: 我想要一个单一的定义 /, /about, /privacy-policy 使用类似的东西 _home: pat
我正在使用 objective-c开发针对 11.4 iOS 的单 View 应用程序,以及 Xcode版本是 9.4.1。 创建后有Main.storyboard和LaunchScreen.stor
我一直在尝试在 shell 程序中实现管道结构,如果我执行简单的命令(例如“hello | rev”),它就可以工作 但是当我尝试执行“head -c 1000000/dev/urandom | wc
此表包含主机和接口(interface)列UNIQUE 组合* 编辑:这个表也有一个自动递增的唯一 ID,抱歉我应该在之前提到这个 ** | host.... | interface..... |
我想将具有固定补丁大小的“std filter”应用于单 channel 图像。 也就是说,我希望 out[i,j] 等于 img[i,j] 附近的像素值的标准值。 对于那些熟悉 Matlab 的人,
假设我想进行网络调用并使用 rx.Single,因为我希望只有一个值。 我如何应用replay().autoConnect() 这样的东西,这样当我从多个来源订阅时网络调用就不会发生多次?我应该使用
我将图像从 rgb 转换为 YUV。现在我想单独找到亮度 channel 的平均值。你能告诉我如何实现这一目标吗?此外,有没有办法确定图像由多少个 channel 组成? 最佳答案 你可以这样做: #
在比较Go和Scala的语句结束检测时,我发现Scala的规则更丰富,即: A line ending is treated as a semicolon unless one of the foll
在IEEE 1800-2005或更高版本中,&和&&二进制运算符有什么区别?它们相等吗? 我注意到,当a和b的类型为bit时,这些coverpoint定义的行为相同: cp: coverpoint a
我正在使用Flutter的provider软件包。我要实现的是为一个 View 或页面提供一个简单的提供程序。因此,我在小部件中尝试了以下操作: Widget build(BuildContext c
我正在尝试在 cython 中使用 openmp。我需要在 cython 中做两件事: i) 在我的 cython 代码中使用 #pragma omp single{} 作用域。 ii) 使用#pra
我正在尝试从转义字符字符串中删除单引号和双引号。它对单引号 ' 或双自动 " 不起作用。 请问有人可以帮忙吗? var mysting = escapedStr.replace(/^%22/g, '
我正在尝试在 cython 中使用 openmp。我需要在 cython 中做两件事: i) 在我的 cython 代码中使用 #pragma omp single{} 作用域。 ii) 使用#pra
我正在使用 ANT+ 协议(protocol),将智能手机与 ANT+ USB 加密狗连接,该加密狗通过 SimulANT+ 连接到 PC。 SimulANT+ 正在模拟一个心率传感器,它将数据发送到
有人可以解释/理解单/多线程模式下计算结果的不同吗? 这是一个大约的例子。圆周率的计算: #include #include #include const int itera(100000000
我编写了一个粗略的阴影映射实现,它使用 6 个不同的 View 矩阵渲染场景 6 次以创建立方体贴图。 作为优化,我正在尝试使用几何着色器升级到单 channel 方法,但很难从我的着色器获得任何输出
尝试使用 Single-Spa 构建一些东西并面临添加到应用程序 AngularJS 的问题。 Angular2 和 ReactJs 工作完美,但如果添加 AngularJS 并尝试为此应用程序使用
我是一名优秀的程序员,十分优秀!