JAVA底子之二-面向对象简述
java底子之二 - 面向对象简述一、概述
假如有机会多接触几种语言,对于步伐员多少是有利益的,至少有助于明白代码的运行真谛。
高级语言有很多是面向对象的,因为面向对象的优点是显而易见的。这里比较知名的有rust,java,c++,c#
但也有很多语言是面向过程的,鼎鼎有名有C,另有如今大家不太熟悉的pascal等。
无论是面向对象还是面向过程,都有自身的优点,这个优点主要是工程上优点,而不是性能、安全上的。
工程优点的意思就是:节约本钱-要么更轻易开辟,要么更有利于维护。
不过很多时候,面向过程的步伐可能会比面向对象的步伐快,这是因为面向过程偏向于解决具体问题,而面向对象偏向于一种模式。
具体到技能细节,此处略,因为内容太多,非本文关注的。
不过面向过程可以明白为绿色通道,而面向对象就是一个个关卡构成的路。绿通肯定比关卡重重来的快。(注意,这仅仅是效果比喻,非结构比喻)
在工程上,假如你制造一个灵活多变,功能强的东西,通常意味着更高的本钱。假如制造了一个专用的,每每意味着专用,高效(某个方面)。
例如瑞士军刀对很多人而言就是个鸡肋,而水果刀就能更好地切削水果。
末了,强调下,这仅仅是面向过程相对面向对象的一个大要优势,具体则取决于工程师的水平,具体的编译器等。
随着各个语言的不断美满,它们会添加一些新的规范/技能来实现面向对象/面向过程的优点,并尽量降服自身的劣势。
假如我们仔细研究各自的发展汗青,就可以验证这一点。但它们终究没有变成对方,是因为各自有不可替换的优势。
JAVA是一种面向对象的语言,但是和C++之类的面向对象又不太一样,主要是因为JAVA要求所有类强制继承于Object。
二、面向对象的核心优点
2.1经典优势
前文已经说过,在性能上,面向对象没有什么优点(至少目前是这样的),以是这里主要说工程优点。
封装
意味着减少了耦合,更少的耦合意味着更轻易复用、扩展,也更轻易设计。
这是因为设计一个类的时候只需要专注于对外袒露什么即可,别的的不用关心。当类设计的简单,那么无论是自身还是别的类的设计
都变得简单。类和类之间只需要遵循约定即可替换(这有助于维护和扩展)
继承与多态
增强了代码重用,降低了编码工作量。
重载
减低了大脑负担,简化了命名,同时也让熟悉这些内容变得相对简单。这是重载的核心优势。不过在别的非oop语言中,重载也根本是可行的。
简而言之,面向对象的核心优势就是:代码复用,增长扩展。降低开辟本钱,降低维护本钱。
关于这个经典优势,很多书籍/资料给的例子都是关于画笔的,这个例子用在这里的确非常符合。
三、面向对象的缺点
以下是通过文心一言收集的缺点,句句在理:
1.复杂性增长
设计和实现复杂度:面向对象设计需要更多的时间和精力来定义类、接口、继承关系、多态等概念。对于小型或简单的项目,这种额外的复杂度可能并不值得。
学习曲线:对于初学者来说,面向对象编程的概念(如封装、继承、多态等)可能比较抽象和难以明白,需要较长的学习曲线。
2.性能开销
运行时开销:面向对象编程中的动态绑定、继承和多态等特性可能会导致运行时性能下降。例如,动态绑定需要在运行时解析方法调用,这可能比静态绑定(即直接调用函数)更耗时。
内存使用:面向对象编程中的对象需要额外的内存来存储对象的元数据(如类型信息、方法表等),这可能会增长内存的使用量。
3.过度设计
设计模式滥用:面向对象编程鼓励使用设计模式来解决问题,但有时候这些设计模式可能会被过度使用或滥用,导致代码变得复杂而难以明白。
过度抽象:为了追求高度的抽象和封装,可能会创建过多的类和接口,使得系统的架构变得庞大而复杂,难以维护。
4.测试难度
依赖关系:面向对象编程中的对象之间可能存在复杂的依赖关系,这使得测试变得更加困难。特殊是在举行单元测试时,需要模拟或隔离这些依赖关系,这可能会增长测试的复杂性和本钱。
状态管理:面向对象编程中的对象通常具有状态,这增长了测试的难度。需要确保在测试过程中对象的状态符合预期,以制止状态不一致导致的测试失败。
5.不得当所有问题
简单问题复杂化:对于一些简单的问题,使用面向对象编程可能会将问题复杂化。例如,对于一些简单的脚本或工具,使用函数式编程或过程式编程可能更加直观和高效。
数据密集型应用:对于数据密集型应用(如大数据处理、机器学习等),面向对象编程可能不是最佳选择。这些应用可能更得当使用更底层、更直接的编程范式(如函数式编程或数据并行处理)。
假如在选择语言之前,以上劣势的确需要考虑。
但是当我们没有选择的时候(假如只会java或者必须用java开辟),那么主要的问题在于:
首先是过度设计,其次是性能开销
如今的很多代码天生就有过度设计的典范问题:天生了过多的类
例如一个CRUD,在后端要包含:
[*]用于dao得Mapper
[*]一个业务接口,一个业务接口实现类
[*]一个控制器
[*]一个实体类
[*]有的时候还要创建各种O,用于传参、展示
虽然这样也有工程上的优点,但是从性能出发而言,根本是没有什么优点。
不怕笑话,假如直接在控制器中用jdbc访问并实现一些逻辑,就能实现一个功能。
虽然我本人不会这么做,但那样的确对于性能有不少帮助。
当然这过度设计的一个小方面,不是过度设计的主要表现:过度的继承、把对象拆分得过于细碎。
假如是按照套路做CRUD,问题不大,因为CRUD根本是代码天生工具天生的,就那样吧。
但是有些系统做复杂了,还是很有一些不简单的CRUD代码。这个时候如何设计类就会有肯定的难度,难度就在于如何制止过度设计。
使用面向对象,很轻易让工程师动不动就创建新的类。 当然这是所有面向对象语言的通病,是天然的伴生品。
至于性能开销,是在java这个框架下的有限优化,可以努力一把,不过不要太费劲。当然这不是说代码乱写。
写CRUD步伐的时候,性能更多时候和JAVA没有什么明显关系(在遵守一些根本要求的前提下),架构师、设计师、dba的作用更加明显。
以是,这也意味着,对大部分工程师是一个利好:毕竟不要费劲心思考虑安全性、性能等等,门槛低了。
如要考虑真实的性能,最好改用别的语言,例如rust,或者jcp考虑使用多平台编译器,并废弃JVM,就像C,C++那样。
阿里的app假如真要考虑性能,早就应该考虑用c,c++之类的语言改写了。
注意:这里性能应该是相对于系统软件、图形、视频处理等软件要求的性能。 应用软件的性能只要不乱写,主要性能大部分取决于数据库。
四、java面向对象的特点(J8前就有)
最大的差别(和C++比较)-不支持多继承
因为不支持多继承,那么一个新的类想使用已有类的能力的时候,只有使用组合模式等方式来实现。
以设计瑞士军刀为例子,假设一把刀山有三个组件:螺丝刀、开瓶器、小刀
如今系统中已经存在了Screwdriver,Bottleopener,Knife
如今要设计Swtool。
实现方式一:新类添加相关类类型的属性(经典组合)
public class Swtool{
private Screwdriver driver;
private Bottleopener opener;
private Knife knife;
....
}这是最简单粗暴的
实现方式二:在实现这个功能的方法中创建对应类示例
public class Swtool{
public void openBottle(){
Bottleopener opener=new Bottleopener();
opener.do();
}
}
别的....
另有更复杂的,例如静态代理,动态代理之类的。
但不管哪一种,工程角度看,好像都没有那么优雅,时间也要浪费一些。
五、J8及之后和类有关的新特性
https://blog.csdn.net/u022812849/article/details/138213697
注意:某些特性可能J8之前就有,记忆不是太清楚了。
5.1、J8对接口的改造
默认方法
目标:减少重复的代码。因为有肯定的概率各个实现类所实现体是一样,或者大部分是一样,这个时候default方法就有利益。
静态方法
目标:工具化接口,使得接口直接具有工具类的作用,作用等同于类的公共静态方法
由于是公共静态的,以是,它可以在任何地方被调用
5.2、J9对接口的改造
私有方法 (默认私有方法)
目标:强化默认方法的功能,减少重复代码,只能被默认方法调用
静态私有方法
目标:私有方法的增强,可以被别的三类接口方法使用(公共抽象的例外,因为只能实例化,实例化后就无法访问了),但也仅限于在接口中。
减少重复代码。
java接口对各种类型方法的支持,虽然增强了灵活性,但是也让接口的特点没有那么光显,某些方面和抽象类过于类似。
我本人有时候会夷由用接口来设计类还是用抽象类来设计接口,关于这个本背面有专门的章节比较接口和抽象类。
我个人猜疑这种演化的必要性。 在不少的高级语言中,就没有接口这这种东西,也能活得好好的。把接口、抽象类、类搞得这么相似,会不会有什么问题?
5.3、J16~17对类的改造
5.3.1、record-记录
这是一个极好用的东西,java把它搞得和javascript中的对象类似,似乎在什么地方都可以定义。
record在效果上类似pojo,但比pojo方便多了。一个是代码少,其次引用属性也更加天然,不要动不动就get,set,更好读一些。
预计在更新的版本中,可以直接通过点语法类访问属性。
这个record和pascal/delphi的record有点相似。
特性:
提高代码可读性和清晰度
促进不变性设计,降低风险
降低编码量,提升效率
不支持继承其他类或接口(除了隐式继承的 java.lang.Record 接口)也就是说,record的出现主要是为了工程目标,而不是出于性能、安全等目标。
由于我们一般不会在record添加方法(可以添加),有些时候还是有一些些不轻易察觉的性能优势,但根本可以忽略不计。
示例
package study.base.oop.record;
/**
* 公共可见
*/
public record TranMessage(String name, String mType, String lvl, String content) {
public String decode() {
String newMsg=content.substring(3);
return newMsg;
}
}
// Human类接接收record类型
package study.base.oop.record;
publicclass Human extends Animal {
private String gender;
private Date birthDay;
private Integer power;
public void sendMessage(TranMessage msg) {
System.out.println(<em>msg.content()</em>);
}
public void readMessage(TranMessage msg) {
System.out.println(msg.decode());
}
public void readBook() {
record Book(
String name,String author,BigDecimal price,String content
) {};
Book lunyu=new Book("论语","孔子",new BigDecimal("100"),"学而不思则罔..");
Bookzz=new Book("左传","左丘明",new BigDecimal("100.908"),"肉食者鄙,未能远谋...");
System.out.println(lunyu.toString());
System.out.println(zz.toString());
}
public static void main (String[] args) {
Human h=new Human();
h.readBook();
}
}
这里演示了record的
[*]定义:可以单独定义(包可见或者public可见),也可以作为内部类存在;也可以在方法内部定义。定义也非常简单类似方法参数
但还是不够方便,还可以继续优化
[*]书写属性-直接 xxx.***(), 这里xxx是变量名称,***指的是属性名称。例如上文 msg.content()
个人觉得还可以继续优化如下:
//定义 -- 可以省掉 {}
public record Message(String title,String content);
//使用 -- 不要用方法访问,而是直接属性访问
record msg=new Message("通知","大家赶紧上学去");
String allWord=msg.title+msg.content; 末了,一句话,record大有可为!
5.3.2、非静态内部类定义静态成员
按照这种趋势,越搞越复杂,虽然某个时候是方便了。
搞得太复杂,老是让我想起两个东西:
[*]简单的美
[*]卡拉什尼科夫自动步枪(AK47)
我觉得,JAVA的进化应该考虑功能效率,性能和安全,不要追求过于边缘的东西。
5.3.3、密封类
注:这一段直接由文心一言天生.
Java的密封类(Sealed Classes)是Java 15中引入的一个预览功能,并在Java 17中成为正式功能。这一特性主要用于增强类的封装性和模块化,限制哪些类可以继承给定的类。
密封类提供了一种机制,使得开辟者能够更准确地控制类的继承层次结构,从而制止意外扩展和可能的错误。
密封类的主要用途和优点包括:
提高封装性:通过限制哪些类可以继承一个给定的类,可以增长类的封装性。这有助于防止类的滥用,确保只有预期的子类才能被创建和使用。
增强模块性:在模块化编程中,密封类可以确保模块之间的依赖关系更加清晰和可控。通过限制哪些模块可以扩展某个类,可以减少模块之间的耦合度,提高系统的可维护性和可扩展性。
促进类型安全:通过使用密封类和接口,可以确保在类型系统中不会出现意外的类型。这有助于在编译时捕获错误,而不是在运行时,从而提高代码的可靠性和可预测性。
优化性能:在某些情况下,密封类可以用于优化JVM的性能。例如,假如JVM知道一个类的所有可能子类,它可以使用更高效的算法来处理多态调用和类型检查。
密封类的根本用法包括:
使用sealed关键字声明一个密封类或接口。
在声明时,指定哪些类可以继承这个密封类或实现这个密封接口(使用permits关键字)。
假如一个类继承了一个密封类,或者一个接口实现了一个密封接口,并且这个类或接口不在permits子句中列出,那么编译器会报错。
根本同意。密封类的主要作用是工程上的,另有一点点性能上的利益。
这是一个新东西,和final修饰符的作用临时没有那么明显的区分。
定义示例:
package study.base.oop.classes.modifier;
public sealed class SealedMan permits SonOfSealedMan {
public void think3TimesOneDay() {
}
}
package study.base.oop.classes.modifier;
publicnon-sealed class SonOfSealedMan extends SealedMan {
}在上例中,封装类的儿子只能是三种之一:依然是封装类、非封装类(肯定要加上non-sealed)、最终类
5.3.4、隐藏类
本人没有用过。毕竟这是JAVA15的特性,而本人直接从J8跳到J17的.
找了不少的参考资料,发现这个东西仅仅用于做框架用的。
这意味着假如做一般的应用开辟,不要过多去考虑。
参考:Java 15中的隐藏类(Hidden Classes) | Baeldung中文网 (baeldung-cn.com)
老美这个文章先容的很清楚了,总结下:
1.作用-搭建架构为主 ,当然假如对峙要用于一般的应用也可以
2.使用途径-定义,编译,然后通过反射天生,再调用
3.创建隐藏类的关键-不在于定义,而是在反射的时候
Class<?> hiddenClass = lookup.<strong>defineHiddenClass</strong>(IOUtils.toByteArray(stream), true, ClassOption.NESTMATE).lookupClass();4.lambda表达式会用到这个功能
老美的文章还提供了和匿名类的比较。
其余略,个人没有用过,就不评价了。
5.4、各种奇怪怪怪的类和名词
java随着发展,创造了许多的类名词。
下表是比较根本的类名词
序号英文名称名称关键字/修饰符使用场景注意事项备注1class类 任何地方无 2abstract class抽象类abstract需要完成抽象工作,构建较复杂的、
提升复用性的
无 3sealed class密封类sealed限定继承关系的,简化维护工作的 4hidden class隐藏类 框架,例如lambda用于框架 5
注:从面向对象的角度而言,普通类和抽象类是根本,别的各种仅仅是为了工程方便而存在的,当然假如把抽象类也那么明白也可以。
这些类和结合上修饰符和作用范围,可以创造出许许多多的的名词。
为了便于行文,不论什么关键字,除了class,别的的都称为修饰符:
根据差别分类,有如下修饰符:
[*]可见范围-public,package,private,protected,final
[*]是否抽象-abstract
[*]继承范围-sealed ,non-sealed
事实上,可见范围,也影响着继承。
记取这些名词和根本内容,在设计的时候,再验证可以如何组合即可。
幸运的时候,现代ide可以在编码阶段就能制止这个问题。
5.3、各种内部类简述
实际上,java允许写各种千奇百怪的内部类。
按照内部类的位置,可以划分为:
[*]类外内部类-这些类和主类同个文件,但是不包含在主类内。不能使用public修饰符,意味着这些类外内部类不能被外部访问
[*]类内内部类-这些类被包含在主类中,可以有各种修饰符。假如使用public,也能被外部实例访问,不过创建上轻微麻烦一些
[*]方法内部类-主要定义在方法中,只能在方法内部使用。别的方法,别的类实例是无法使用这些的。
a.以上三种位置的,只有类内内部类是可以被别的类的实例访问的,另外两种都不行
b.类外内部类和类内内部类的主要区别之二:前者不能被儿孙继承
总的来说 ,内部类就是为制止工程上对外袒露不好看,外部不必要看的内容。当然假如想的话,有可以添加public修饰符。
定义在哪里,对系统的性能和安全根本没有啥影响,主要出于工程的目标-眼不见为净!!!
在spring的代码中,不乏使用内部类的情况。
但我个人并不喜欢过多使用内部类,除非这个内部类不为了继承用。
偶尔也会用上,主要是为了让代码好看一些,不想让主类变得过于痴肥,但同时也不盼望别的功能使用这些特定的功能,以是就可能会定义
一些内部类来用。
内部类一旦考虑到继承,就变得有点杂乱。
以下的例子,演示了让人烦乱的内部类及其继承:
package study.base.oop.classes.inner;
/**
* 令人目瞪口呆的各种内部类
*/
public class InnerMan {
public void work() {
final class Tool{
void show() {
System.out.println("拔出我的家伙...");
}
}
class Target{
void show() {
System.out.println("这是一个必须完成的任务");
}
}
record Job(Tool tool,Target target) {};
class JobDetail{
static void doJob(Job job) {
job.tool.show();
job.target.show();
}
}
Job job=new Job(new Tool(),new Target());
JobDetail.doJob(job);
}
static class Head{
Integer weight;
Head(Integer weight){
this.weight=weight;
}
}
/**
* 爱好
*/
public abstract classFav{
}
/**
* 活
*/
sealed class Work permits HomeWork,InnerFoodWork{
}
final class FoodFav extends Fav{
}
final class HomeWork extends Work{
}
public non-sealed class InnerFoodWork extends Work{
}
}
/**
* 定义在同个文件的其它类,不能使用public修饰符,意味着这些类外内部类不能被外部访问
*/
/**
* 爱好
*/
abstract classOuterFav{
}
/**
* 活
*/
sealed class Work permits HomeWork,OuterFoodWork{
}
final class FoodFav extends OuterFav{
}
final class HomeWork extends Work{
}
non-sealed class OuterFoodWork extends Work{
}
InnerMan的子类:
package study.base.oop.classes.inner;
/**
* 孩子如何使用父亲的内部类?
*
* 内部用内部/外部,外部用外部
*/
public class SonOfInnerMan extends InnerMan {
public void learn() {
//可以实例化父亲的类内部类
Head head=new Head(67);
}
//内部继承内部
classSonHead extends Head{
SonHead(Integer weight) {
super(weight);
}
}
//内部继承外部
final class SonFoodWork extends OuterFoodWork{
}
}
//外部继承外部
final class SonFoodWork extends OuterFav{
}考虑到17中可以使用record,我会尝试多用一些内部类,反正应用类型的软件对性能不会有什么苛刻的要求。
六、比较抽象类和接口
接口有什么用? 应该是为了模拟多继承,其次就是为了便于模块化,并对外只袒露必须的内容。
假如没有接口,那么要做模块开辟就会变得麻烦。
有了接口,实现多态很轻易,插件开辟等等也轻易。
更具体的见网友总结:
特性抽象类接口定义一种不能被实例化的类,可以包含抽象方法和具体方法,以及成员变量(包括常量和普通变量)一种完全抽象的类型,只能定义方法的签名(即方法的返回类型、方法名和参数列表),不能包含方法的实现,只能包含常量(即被`final`修饰的成员变量)实现方式抽象类可以有部分方法的实现,也可以完全没有实现(仅包含抽象方法)接口只能定义方法的签名,不能有方法的实现继承关系一个类只能继承一个抽象类(Java不支持多继承类)一个类可以实现多个接口,实现多重继承的效果构造函数抽象类可以有构造函数,用于初始化抽象类的成员变量,但构造函数不能被实例化接口不能有构造函数,因为接口不能被实例化方法修饰符抽象类中的方法可以使用`public`、`protected`、`default`(包级私有)、`private`等访问修饰符,但抽象方法通常使用`public`或`protected`接口中的方法默认为`public`,且不能用其他修饰符(如`private`、`protected`、`default`)修饰默认实现抽象类可以包含具体方法的实现,子类可以选择继承这些实现或重写它们接口中的方法都是抽象的,没有默认实现,实现接口的类必须提供所有方法的具体实现静态方法抽象类中可以定义静态方法,这些方法属于类自己,而不是类的实例接口中不能定义静态方法(从Java 8开始,接口中可以通过默认方法或静态方法提供实现,但这里主要讨论传统意义上的接口)使用场景适用于定义类的结构,提供一些共同的属性和方法,可以作为多个子类的父类。当需要提供一些方法的默认实现时,使用抽象类更为符合适用于定义类的行为,定义一组相关的功能,实现类可以根据需要实现多个接口。当需要实现多重继承时,必须使用接口
适用于插件开辟
注:上表是比较传统的抽象类和接口。 假如是j8之后的,就不太适用了,因为j8之后,接口已经变得复杂了。
随着java把接口搞得越发复杂,好像抽象类已经没有什么存在的意义了....
选择抽象类还是接口,还是有点小门道的,值得另外开篇说这个问题。 本文就先这样吧!
七、JAVA类开辟体验
本人用过的语言比较多,大概有10来种。java算是比较久的,当前也以java为主。
总体而言,对java的类还算满意,就是盼望不要把java变成javascript,也不要把类搞得过于复杂了,这些方向可能不利于java的发展。
工作的时候,大部分时候都是一些CRUD机械行活,没有挑衅性,只有偶尔设计一些复杂一些东西的时候才会考虑用上类的各种特性,包括继承、封装、多态等等。
java的接口也还可以,虽然这不是java独有的。
八、小结
java能够实现面向对象编程的根本目标:抽象、封装、继承、多态
这些特性的实现,对于实现工程目标(复用、扩展、适当的安全)还是有用的。
和别的OOP语言一样,JAVA同样存在有天然的缺陷:过度设计、性能劣势
JVM的存在,让java语言变得更慢。以是如今只要优化JVM(步伐员只要重新编译下即可),就能让有关JAVA步伐获得明显的提升。某些方面可以说前期JVM设计还是很不上心的。
至于不能适用于所有场景、复杂化、不利于测试,每种OOP语言都有这样的问题。
随着java的快速推进,近年java和类(含接口)有关的特性让人有点目不暇接。
有点复杂化倾向,可能不是太好,例如隐藏类。
record的出现绝对是一个好事,不过值得再简化下。
末了java的接口如今变得复杂且让人印象深刻,而内部类则有点让人望而生畏,不过适当使用好像也还可以!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]