Java基础知识

目录

 

1.对java平台的理解

2.Exception和Error的区别

3.final, finally, finalize

4.引用和可达性

5.String,StringBuffer,StringBuilder有什么区别?

6.动态代理是基于什么原理

7.int和Integer的区别

8.Vector、ArrayList、LinkedList有何区别?

9.Hashtable,HashMap,TreeMap区别?

10.如何保证容器是线程安全的?ConcurrentHashMap如何实现高效的线程安全?

11.Java提供了哪些IO方式?NIO如何实现多路复用?

12.Java有几种文件拷贝?哪一种更高效?

13.谈谈接口和抽象类有什么区别?

14.谈谈你知道的设计模式?


1.对java平台的理解

Java是一种面向对象的语言,显著的特性有两个方面:

一是“write once, run anywhere", java的跨平台特性使得java源代码不需要针对不同的平台进行修改,如c语言需要根据平台的不同进行修改。

二是垃圾收集(GC),Java通过垃圾收集器回收内存。

java是解释机制还是编译机制?

定义: 
编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,可直接运行这个程序。 如c,编译一次后就是可运行的机器码。
解释型语言:把做好的源程序翻译一句,然后执行一句,直至结束! 

java是编译-解释-执行的过程,代码首先需要通过编译成为字节码(class),然后通过jvm虚拟机内嵌的解释器将字节码转换成机器码。一定要区分的话,倾向于解释型语言,因为第一次编译的字节码时本地系统无法执行的,需要通过jvm的解释执行。

同时,也存在JIT编译器(即时编译器,动态翻译成机器码)和jdk9的AOT编译器,能够将代码编译成机器码。

java跨平台的特性并不是说java语言可以跨平台,而是因为jdk提供了一个java语言的运行环境,程序可以在jvm虚拟机运行。

 

2.Exception和Error的区别

ClassNotFoundException和NoClassDefFoundError的区别?

一个是exception,一个是error。

一般是使用Class.forName()、ClassLoader.loadClass()等动态加载类时找不到类抛出异常,

编译时能找到,但是运行时找不到类抛出error,比如运行new时。可能由于打包遗漏了某个包下的类。

异常处理:

一、尽量不要捕获Exception这样的通用异常,而是应该捕获特定异常

二、尽量不要生吞异常,对异常的打印应该输出到日志中

从性能角度看,

try-catch会产生额外的性能开销,建议仅捕获必要的代码段,不要try包裹住整段代码,与此同时,用异常控制流程是比较低效的。

Java每实例化一个Exception,都会对当前的栈进行快照,这是一个比较比较重的操作。

 

3.final, finally, finalize

final可以用来修饰类,方法,变量,分别有不同的意义,final修饰的class代表不可以继承扩展,final的变量是不可以修改的,而final的方法也是不可以重写的

finally则是java保证重点代码一定要被执行的一种机制

finalize是基础类object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。

 

4.引用和可达性

关于对象的生命周期

引用:强引用,软引用,弱引用,虚引用(幻象引用)

判断对象可达性,是JVM垃圾收集器决定如何处理对象的一部分考虑

 

5.String,StringBuffer,StringBuilder有什么区别?

String是Java语言非常基础和重要的类,提供了构造和管理字符串的各种基本逻辑。string是final不可变的,类似拼接、裁剪字符串等动作,都会产生新的对象。

StringBuffer是为了解决上面提到的拼接产生太多中间对象的问题而提供的一个类。StringBuffer本质是一个线程安全的可修改字符序列,保证了线程安全,也随之带来了额外的性能开销,

所以除非有线程安全的需要,不然还是推荐使用它的后继者,也就是StringBuilder

StringBuilder能力上和StringBuffer没有区别,但是去掉了线程安全的部分,有效减小了开销,绝大部分情况下的首选。

 

6.动态代理是基于什么原理

编程语言通常有各种不同的分类角度,动态类型和静态类型就是其中一种分类角度,简单区分就是语言类型信息是在运行时检查,还是编译器检查。

与其近似的还有一个对比,就是强类型和弱类型,就是不同类型变量赋值时,是否需要显式地(强制)进行类型转换。

通常认为,java是静态的强类型语言,但是因为提供了类似反射等机制,也具备了部分动态语言的能力。

Java反射机制,动态代理是基于什么原理

反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省的能力。通过反射我们可以直接操作类或对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者

构造对象,甚至可以运行时修改类定义。

动态代理时一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装RPC调用、面向切面的编程(AOP)。

实现动态代理的方式很多,比如JDK自身提供的动态代理,主要利用的反射机制。还有其他的实现方式,如ASM, cglib(基于ASM),Javassist等。

自己理解:
Java是静态强类型语言

反射:在运行时可以获取类,并操作或修改,提供了一种动态加载(运行时装配,通过class对象获取对象),动态实现的能力和技术

代理:对被代理类的一种包装,访问者通过代理类实现对访问对象的调用,代理类可以做到预处理,过滤,传递,返回消息等等,本身不实现服务

静态代理:通过定义代理类,在编译时实现对类的包装,缺点时一个静态代理只能代理一个类

动态代理:通过反射等技术实现在运行时创建代理类  https://www.jianshu.com/p/9bcac608c714

一个基础类-->需要做处理-->代理-->静态代理实现操作-->获取代理对象-->ok

一群基础类-->做同样的处理-->代理-->动态代理实现操作-->根据className获取具体的类的代理对象-->ok

 

7.int和Integer的区别

int是8个基本数据类型之一。Integer是int的包装类。

(1)自动拆装箱发生在编译器,即Javac之后

(2)编程中,尤其对于性能敏感的场合,使用基本数据类型会更有优势,避免创建过多对象

什么时候使用Integer?

数据计算的时候用基本数据类型,业务相关使用Integer。如定义类字段类型使用Integer,在和数据库字段映射时,如果返回为null,定义为int类型会报错,因为int默认为0。

使用Integer进行非空判断,直接对null判断也更方便。

 

8.Vector、ArrayList、LinkedList有何区别?

集合:集合类是用来存储某类对象的

数组:可以存储对象和基本类型,但是数组的长度不可变,且只能存储一种对象。

都是为了高效的管理和操作数据,集合就是对数组的一种补充

集合分成两大类:一是实现Collection接口,二是实现Map接口

List,也就是有序集合。

Set,Set是不允许重复元素的,这是和List最明显的区别。适用于在日常开发中保证元素唯一性的场合。

Queue/Deque,则是Java提供的标准队列结构的事项,除了集合的基本功能,还支持类似于先入先出,后入后出等特定行为。

Vector、ArrayList、LinkedList

这三者都是实现集合框架中的List,也就是所谓的有序集合。

Vector是Java早期提供的线程安全的动态数组。Vector内部是使用对象数组来保存数组,可以根据需要自动的增加容量,当数组已满时,会创建新的数组,并拷贝原有的数组数据。

ArrayList是应用更加广泛的动态数组实现,它本身不是线程安全的,所以性能要好得多。与Vector近似,ArrayList也是可以根据需要调整容量,不过两者的调整逻辑有所区别,Vector

在扩容时会提高1倍,而ArrayList则是增加50%

LinkedList顾名思义是Java提供的双向链表,所以它不需要像上面两种那样调整容量,它也不是线程安全的

数组和链表

数组:适合随机访问,根据索引方便获取,但是添加或者删除复杂,需要移动后面所有的元素。

链表:方便添加或者删除,检索到数据,只需要更新node的pre和next指向即可,但是查询比较复杂,需要从前往后或者从后往前遍历。

所以开发中,如果事先可以估计到,应用操作是偏向与插入、删除,还是随机访问较多,就可以针对性的进行选择。这也是面试常见的一个考察角度,给定一个场景,选择合适的数据结构

单向链表和双向链表:https://blog.csdn.net/cxh6863/article/details/104799231

需要掌握的排序算法:

  • 内部排序,如归并排序、交换排序(冒泡、快排)、选择排序、插入排序等。
  • 外部排序,掌握利用内存或外部存储处理超大数据集,至少要理解过程和思路。

LeetCode

实现一个云计算任务调度系统,希望可以保证VIP客户的任务被优先处理,可以利用哪些数据结构或者标准的集合类型呢?更近一步讲,类似场景大多是基于什么数据结构?

由于任务有前后顺序关系,所以首先想到的是使用优先队列。使用PriorityQueue,将VIP用户的优先级设置为最高,优先处理。对于其他用户,还可以设计各种公平的优先级选择算法,与PriorityQueue结合使用。

类似场景大多就是基于队列的数据结构了。实际工具的话,消息队列(MQ)就是很直接的例子了。可以使用消息队列对用户请求进行削峰操作,前台快速响应,后台进行处理操作。

队列和消息队列

 

9.Hashtable,HashMap,TreeMap区别

Hashtable,HashMap,TreeMap都是最常见的一些Map实现,是以键值对的形式存储和操作数据的容器类型。

Hashtable本身是同步的,不支持null的键和值,由于同步导致的性能开销,已经很少被推荐使用了。

HashMap应用更广泛,行为上和Hashtable大致一致,区别是不同步的,支持null的键和值,是绝大部分利用键值对存取场景的首选。

TreeMap则是基于红黑树的一种提供顺序访问的Map。具体顺序可以由指定的Comparator来决定,或者根据键的自然顺序来判断。

针对有序map的补充:LinkedHashMap和TreeMap

LinkedHashMap: 通常提供的是遍历顺序符合插入顺序,它的实现是通过为键值对维护一个双向链表。例如,需要构建一个空间占用敏感(容量有限)的资源池,希望将不常用的对象释放掉,就可以利用

LinkedHashMap实现。

HashMap源码分析:

1)定义的变量,如容量,负载系数,阈值等含义

2)数据结构,数组+链表

3)lazy-load,初始化的时候首次加载

4)hashMap的hash()方法为什么要返回 (h=key.hashcode()) ^ (h >>> 16)?

这是因为键值对在哈希表中的位置(数组index)取决于为计算 i = (n-1) & hash,那么HashMap里的哈希寻址是忽略容量以上的高位,有些数据计算出的哈希值差异主要在高位,这种处理就可以有效避免

类似情况下的哈希碰撞。

5)resize(),扩容的阈值=负载因子x容量,每次扩容的容量都是2倍,扩容后,需要将老的数组中元素重新放置到新的数组。

6)为什么要树化?

本质是性能问题,哈希碰撞频繁,导致链表过长,查询时间陡升,黑客可以利用这个漏洞来攻击服务器,让服务器CPU大量占用,从而引起安全问题。而树化(红黑树)可以将时间复杂度降到O(logn),

  • 从而避免查询时间过长。

7)解决哈希冲突有哪些方法?

  • 开放寻址法:通过算法再寻找一个空桶
  • 链地址法:HashMap的解决方法,将hash冲突的值放在一个链表中
  • 再哈希法
  • 建立一个公共溢出区:将冲突的放在另外一个地方

 

10.如何保证容器是线程安全的?ConcurrentHashMap如何实现高效的线程安全?

Java提供了不同层面的线程安全支持。在传统的集合框架内部,除了Hashtable等同步容器,还提供了所谓的同步包装器(Syschronized Wrapper),我们可以调用Collections工具类提供的包装方法,来

获取一个同步的包装容器(如Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。

另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:

  • 各种并发容器,比如ConcurrentHashMap, CopyOnWriteArrayList.
  • 各种线程安全队列(Queue/Deque),如ArrayBlockingQueue,SynchronousQueue。
  • 各种有序容器的线程安全版本等。

具体保证线程安全的方式,包括有从简单的sychronize方式,到基于更加精细化的,比如基于分离锁实现的ConcurrentHashMap等并发实现等。具体选择要看开发的场景需求,总体来说,并发包内提供的

容器通用场景,远优于早期的简单同步实现。

1)为什么需要CurrentHashMap?

Hashtable本身比较低效,因为它的实现基本就是将put、get、size等各种方法加上“synchronized”。简单来说,这就导致了所有的并发操作都要竞争同一把锁,一个线程在进行同步操作时,其他线程只能等待,

大大降低了并发线程的效率。

HashMap不是线程安全的,并发情况会导致类似CPU占用100%等一些问题。那么能不能利用Collections提供的同步包装器来解决问题呢?

同步包装器只是利用输入Map构造了另一个同步版本,所有操作虽然不再声明为synchronized方法,但是还是利用了this作为互斥的metex,没有真正意义上的改进!

所以,Hashtable或者同步包装版本,都只是适合在非高并发的场景下。

2)CurrentHashMap解析

1.7 

  • 分段锁,Segment将map内部进行分段,在进行并发操作时,只需要锁定相应段,这样就有效的避免了类似Hashtable整体同步的问题,大大提高了性能。
  • HashEntry内部使用volatile的value字段保证可见性

1.8 

  • 总体结构上,内部存储和HashMap内部结构非常相似
  • 内部仍然有Segment定义,但仅仅时保证序列化时的兼容性,不再有任何结构上的用处
  • 因为不再使用Segment,初始化大大简化,修改为lazy-load,有效避免了开销
  • 数据存储利用volatile保证可见性
  • 使用CAS等操作,在特定场景进行无锁并发操作
  • 使用Unsafe、LongAddr之类底层手段,进行极端情况的优化

总体来说,相比与Hashtable, CurrentHashMap都是采用更加细粒度的同步手段提升性能。

同理,在开发中,避免采用大面积的同步或者其他严重影响性能的代码。

思考:在产品代码中,有没有经典的场景需要使用类似ConcurrentHashMap这样的开发容器呢?

 

扩展: 可见性与volatile

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。前面一个操作的结果对后续操作是可见的

被volatile修饰的变量写对读是可见的,确保线程在执行读操作时始终拿到的是新值。同时volatile禁止重排序功能,被volatile修饰的变量在进行读写时,这些变量是不能重排序;volatile变量前后的读写操作不能重排序

声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

Java中Volatile关键字详解https://www.cnblogs.com/zhengbin/p/5654805.html

导致可见性的原因是缓存,导致有序性的原因是编译优化,那解决可见性、有序性最直接的办法就是禁用缓存和编译优化,但是这样问题虽然解决了,我们程序的性能可就堪忧了。

合理的方案应该是按需禁用缓存以及编译优化volatile就是通过直接读取内存实现可见性,通过设置内存屏障禁止重排序.

 

 

11.Java提供了哪些IO方式?NIO如何实现多路复用?

BIO,NIO,NIO2

  • 传统的java.io包,它基于流模型实现,提供了我们最熟知的一些IO功能,比如File抽象、输入输出流等。交互方式时同步、阻塞的方式(Block IO)。也就是说,在读取或者写入的时候,线程会一直阻塞在那里,他们之间的调用是可靠的线性顺序。

java.io包的好处是代码比较简单、直观,缺点则是IO效率和扩展性存在

  • 在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层的高性能数据操作方式。
  • 在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,

当后台处理完成,操作系统会通知相应线程进行后续工作。

 

考虑问题:

传统IO和NIO的组成是什么?

什么场景下使用哪种IO方式?

NIO的高效操作原理是什么?怎么实现的NIO?

NIO会有哪些问题以及可以完善的地方?

 

概念:

阻塞的是线程(线程无法再执行其他程序了)

 

  • 区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,

其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系

  • 区分阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;

非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理

 

对java.io的了解:

1)IO 不仅仅是对文件的操作,网络编程中,比如 Socket 通信,都是典型的 IO 操作目标。

2)输入输出流是对字节的操作

3)Reader/Writer是对字符的操作

4)BufferedOutputStream 等带缓冲区的实现,可以避免频繁的磁盘读写,进而提高 IO 处理效率。这种设计利用了缓冲区,将批量数据进行一次操作,但在使用中千万别忘了 flush。

BufferedOutputStream类是高效字节缓冲输出流类,底管理一个字节数组,通过减少操作系统调用,从而大大提高写入效率。

5)很多 IO 工具类都实现了 Closeable 接口,因为需要进行资源的释放。IO资源一定要释放

 

 

NIO:

首先,熟悉一下 NIO 的主要组成部分: (高效数据容器, 管道, 多路复用)

  • Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
  • Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,

这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过 Socket 获取 Channel,反之亦然。

  • Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理
  • Charset,提供 Unicode 字符串定义,NIO 也提供了相应的编解码器等,例如,通过下面的方式进行字符串到 ByteBuffer 的转换

 

NIO能解决什么问题?

1.socket通信

1)使用io实现一个socket服务器,每一个client启动一个线程

问题:启动或关闭线程都是有明显开销的,每个线程都有单独的线程栈等结构,需要占用非常明显的内存,所以,每一个client启动一个线程非常浪费。

2)使用线程池,通过一个固定大小的线程池,来负责管理工作线程,避免频繁创建、销毁线程的开销,这是我们构建并发服务的典型方式。这种工作方式,可以参考下图来理解。

如果连接数并不是非常多,只有最多几百个连接的普通应用,这种模式往往可以工作的很好。但是,如果连接数量急剧上升,这种实现方式就无法很好地工作了,因为线程上下文切换开销会在高并发时变得很明显,这是同步阻塞方式的低扩展性劣势。

3)NIO多路复用

首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色。

然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求。

注意,为什么我们要明确配置非阻塞模式呢?

这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常。Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒。在 sayHelloWorld 方法中,

通过 SocketChannel 和 Buffer 进行数据操作,在本例中是发送了一段字符串。

可以看到,在前面两个样例中,IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,

频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。下面这张图对这种实现思路进行了形象地说明。

(注释:上述方法中解决阻塞问题,1是采用多线程,2线程池解决了线程频繁启动与关闭的开销,3NIO解决了线程之间频繁切换的消耗),但是本质上感觉还是同步,阻塞,只是进行了优化。将线程的消耗转移到channel的切换执行(按序执行)上。

4)NIO2,异步回调

 

对比BIO和NIO

对比BIO,NIO,NIO2(AIO)的理解: 一个快递员送货

BIO就是一定要送到客户手中, 确定收货完成, 否则一直等待.

NIO就是送货到某个地点, 然后每隔30分钟来亲眼看一下是否客户确认收货了, 期间可以去干别的事情.

NIO2就是我送到收货地点了,  那么默认收货人已经收货. 不用管了. 实际结果客户会返回状态通知.

 

关于线程状态:

https://blog.csdn.net/pange1991/article/details/53860651

 

12.Java有几种文件拷贝?哪一种更高效?

有几种比较典型的:

1)利用 java.io 类库,直接为源文件构建一个 FileInputStream 读取,然后再为目标文件构建一个 FileOutputStream,完成写入工作。


public static void copyFileByStream(File source, File dest) throws
        IOException {
    try (InputStream is = new FileInputStream(source);
         OutputStream os = new FileOutputStream(dest);){
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
    }
 }

 

2)利用 java.nio 类库提供的 transferTo 或 transferFrom 方法实现。


public static void copyFileByChannel(File source, File dest) throws
        IOException {
    try (FileChannel sourceChannel = new FileInputStream(source)
            .getChannel();
         FileChannel targetChannel = new FileOutputStream(dest).getChannel
                 ();){
        for (long count = sourceChannel.size() ;count>0 ;) {
            long transferred = sourceChannel.transferTo(
            sourceChannel.position(), count, targetChannel);                        
            sourceChannel.position(sourceChannel.position() + transferred);
            count -= transferred;
        }
    }
 }

当然,Java 标准类库本身已经提供了几种 Files.copy 的实现。对于 Copy 的效率,这个其实与操作系统和配置等情况相关,总体上来说,NIO transferTo/From 的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切换。

 

如何提高类似拷贝等 IO 操作的性能,有一些宽泛的原则:

  • 在程序中,使用缓存等机制,合理减少 IO 次数(在网络通信中,如 TCP 传输,window 大小也可以看作是类似思路)。
  • 使用 transferTo 等机制,减少上下文切换和额外 IO 操作。
  • 尽量减少不必要的转换过程,比如编解码;对象序列化和反序列化,比如操作文本文件或者网络通信,如果不是过程中需要使用文本信息,可以考虑不要将二进制信息转换成字符串,直接传输二进制信息。

 

13.谈谈接口和抽象类有什么区别?

接口: 接口是行为的抽象,它是抽象方法的集合.接口是不能实例化的, 只有抽象方法和静态方法.

接口是对公共行为的声明.

//定义接口
public interface InterfaceTest {

    void test01();

    static void test02(){
        System.out.println("interface_test02===============");
    }
}

//定义实现类
public class InterfaceTestImpl implements InterfaceTest {
    @Override
    public void test01() {
        System.out.println("interfacetest01===========");
    }
}

//测试
public class MainTest {

    public static void main(String[] args) {
        InterfaceTest.test02();
        InterfaceTest interfaceTest=new InterfaceTestImpl();
        interfaceTest.test01();
    }
}

输出:
interface_test02===============
interfacetest01===========

抽象类: 抽象类是不能实例化的类,用 abstract 关键字修饰 class,其目的主要是代码重用.

可以有一个或者多个抽象方法,也可以没有抽象方法。抽象类大多用于抽取相关 Java 类的共用方法实现或者是共同成员变量,然后通过继承的方式达到代码复用的目的。

//定义抽象类
public abstract class AbstractTest {

    public abstract void  test01();

    public void test02(){
        System.out.println("1231244");
    }
}

//定义子类
public class AbstractTestEx01 extends AbstractTest {
    @Override
    public void test01() {
        System.out.println("test01=============");
    }
}

//测试
public class MainTest {
    public static void main(String[] args) {
        AbstractTest test=new AbstractTestEx01();
        test.test01();
        test.test02();
    }
}

输出:
test01=============
1231244

抽象类可以实现对公共行为(test01)的抽象, 也可以实现代码的重用(test02).

 

14.谈谈你知道的设计模式?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值