本篇博客主要记录Java中面向对象的概念和Java语法的基础知识。
面向对象
什么是面向对象
面向对象是一种优秀的软件设计思想,是相对于面向过程、面向切面等设计思想的一种软件设计理念。它的核心思想是运用更加贴近人类思维的方式去设计软件,将软件中的各个组件抽象成相应的类,再将这些类组装成我们所需的软件系统。
这里举个例子:假如用面向对象的方式设计一个电脑,我们会设计CPU类、硬盘类、显示器类、内存类等等,然后将这些类组合在一起设计成Computer类。
而面向过程的思想在解决问题时会将问题分解成一个个小的函数,然后按照某种顺序去执行这些方法,当这些方法执行完毕,问题也就解决了。
三大基本特征和五项基本原则
面向对象的三大基本特征是:封装、继承和多态。正是基于这些特征,面向对象的开发语言才能拥有更好的可重用性、扩展性和维护性。
- 封装:将对象的实现细节隐藏起来,然后通过一些公共的方法向外部提供该对象的功能;
- 继承:继承是软件复用的一种重要手段(组合和代理也是代码复用的手段),子类继承父类之后将直接获得父类的属性和方法;
- 多态:对象可以赋给父类对象或者它实现的接口,但是运行时依然表现出子类或实现类的特征。
面向对象的五大原则如下:
- 单一职责原则(SRP):一个类专注于实现一个功能;
- 开闭原则(OCP):对象或实体应该对扩展开放,对修改封闭;
- 里氏替换原则(LSP):子类可以替换父类并且出现在父类能够出现的任何地方(这个原则就需要我们面向接口编程);
- 依赖倒置原则(DIP):高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象,抽象不应该依赖于具体实现,具体实现应该依赖于抽象;
- 接口隔离原则(ISP):使用多个专门的接口比使用单个接口要好的多:在实际编程中,为了减少接口的定义,将许多方法都放在一个接口中,最后发现,维护和实现接口的时候会花费很多精力,接口所定义的操作相当于对客户端的一种承诺,这种承诺当然是越少越好,越精练越好,过多的承诺带来的就是你的大量精力和时间去维护。不要把不相干的方法放在一个接口中定义。
另外还有迪米特原则和合成服用原则,也需要我们在设计系统和接口时考虑到。
迪米特法则:又叫最少知识法则,这个法则主张一个类要尽可能少地知道其他类地细节,并且尽可能少地和其他类进行通信交互,只和于自己有密切关系地类进行交互。
合成服用原则:合成服用原则又称为组合/聚合服用原则(Composite/Aggregate Reuse Principle):尽量采用组合(contains-a)、聚合(has-a)的方式而不是继承(is-a)的关系来达到软件的复用目的。合成服用原则最主要地原因是继承会破环封装:基类的很多内部细节都是对派生类可见的,而有些细节可能是不想让子类知道的。关于继承、组合还有聚合的概念可能比较容易搞混,这边也提下。
继承:子类拥有父类的某些属性和方法,子类可以替换父类;
组合:是整体与部分的关系,整体离不开部分,部分离开了整体没有意义,如飞机翅膀与飞机的关系;
聚合:也是整体与部分的关系,但整体可以分离部分,部分也可以离开整体,如火车与车厢的关系,还有就是聚合支付的列子等。
重载和重写的区别
重载:是指在一个类里面存在多个方法名相同,但是方法参数不同的方法。
重写:是指子类在继承父类时,重新改写了父类的某个方法。子类中的这个方法的方法名和方法参数和父类中的完全一样。
Java基础语法
Java语言是最流行的编程语言,从刚“出生”,Java语言就号称“一次编译,到处运行”。
Java语言能跨平台的关键是JVM能跨平台,JVM屏蔽了操作系统的底层差异,让字节码文件在每个平台上都可以执行。
除了Java,JVM还支持的语言有Kotlin、Groovy、JRuby、Jython、Scala等。另外,随着Oracle对Graal虚拟机的研发,JVM平台将会支持所有的语言,包括JS和C++等语言。
进行Java开发的第一步是下载一个合适版本的JDK,然后配置JAVA_HOME这个环境变量。但是如果你只是需要运行Java程序,那你只需要安装一个JRE即可。JDK和JRE的关系如下:
JDK包含:Java编译器、JRE以及常用的Java工具库;
JRE包含:JVM、类加载器、字节码校验器以及核心类库等。
然后就是编译执行Java程序
# -d后面的参数指定生成的字节码文件的生产路径,默认当前目录;
javac -d destDir HelloWorld.java;
# 执行Java程序;
# classpath的作用是提供了一系列路径让Java程序去在这些路径下寻找类,找不到就报错。在Windows上用;号分割,在Linux上用:号分隔
java -classpath %CLAss_PATH%;.;dir1;dir2 HelloWorld;
三种基本的程序结构
- 顺序结构
- 选择结构
- 循环结构
使用这三种结构能编写出任意功能的程序。
Java中的注释
- 单行注释://
- 多行注释:/**/
- 文档注释:/** */ 使用javadoc可以生成api文档
Java中的关键字
Java中一共有48个关键字,2个保留字和三个直接量
- 访问控制:private、protected、public;
- 类,方法和变量修饰符:abstract、class、extends、final、implements、interface、native、new、static、strictfp、synchronized、transient、volatile;
- 程序控制:break、continue、return、do、while、if、else、for、instanceof、switch、case、default;
- 错误处理:try、cathc、throw、throws、finally;
- 包相关:import、package;
- 基本类型:boolean、byte、char、double、float、int、long、short、null、true、false;
- 变量引用:super、this、void;
- 保留字:goto、const;
- 直接量:true、false、null。
重要关键字说明:
- native: 用来声明一个方法是由与机器相关的语言(如C/C++/FORTRAN语言)实现的;
- strictfp:用来声明FP-strict(双精度或单精度浮点数)表达式;
- transient:声明不用序列化的属性;
- volatile:表明两个或多个变量必须同步地发生变化。
数据类型
Java中的数据类型分为基本数据类型和引用数据类型。
1. 取值范围
8种基本数字类型从大类上分的话分别是布尔型,字符型,整形和浮点型,对应bool,byte,char,short,int,long,float和double类型。
- byte:一个字节,8个bit位,-2^7 ~ 2^7-1
- char : 2个字节,16个bit位,0 ~ 2^16-1 ;
- short:2个字节,16个bit位,-2^15 ~ 2^15-1
- int:4个字节,32个bit位,-2^31 ~ 2^31-1
- long:8个字节,64个bit位,-2^63 ~ 2^63-1
- float:4个字节,32个bit位
- double:8个字节,64个bit位。
需要注意的是:两个相同类型的数据相加后的值可能会超过其最大的取值范围,这边在我们开发过程中一定要注意~
上面的float和double都是浮点类型数据,在计算机中,浮点数用来近似表示任意某个实数。浮点数分为双精度浮点数和单精度浮点数。单精度占4个字节,双精度占8个字节,表示的范围更大。
对于double和float类型的数据,正数除以0得到正无穷,负数除以0得到负无穷,0.0除以0达到NaN。但是整数除以0会得到除0异常。
在要求精确计算的场合,不建议使用浮点型数据。因为浮点数计算的结果不是很精确,是近似的结果。这种情况应该使用BigDecimal(或者金额用分作为单位,以Long存储)。这边举个BigDecimal使用的简单列子:
BigDecimal bigDecimal1 = new BigDecimal(String.valueOf(1.1));
BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(2.223));
String str1 = bigDecimal1.add(bigDecimal2).toString();
//减法
bigDecimal1 = new BigDecimal(String.valueOf(2.2));
bigDecimal2 = new BigDecimal(String.valueOf(10.1));
String str2 = bigDecimal1.subtract(bigDecimal2).toString();
//乘法
bigDecimal1 = new BigDecimal(String.valueOf(1.1));
bigDecimal2 = new BigDecimal(String.valueOf(2.234));
String str3 = bigDecimal1.multiply(bigDecimal2).toString();
//除法
bigDecimal1 = new BigDecimal(String.valueOf(4.4));
bigDecimal2 = new BigDecimal(String.valueOf(3));
//除法需要设置精度和四舍五入的方式
String str4 = bigDecimal1.divide(bigDecimal2,3, RoundingMode.HALF_UP).toString();
//结果
System.out.println("加法结果:" + Double.valueOf(str1));
System.out.println("减法结果:" + Double.valueOf(str2));
System.out.println("乘法结果:" + Double.valueOf(str3));
System.out.println("除法结果:" + Double.valueOf(str4));
2. 基本数据类型之间的转换
char---------------|
byte-->short-->int-->long-->float-->double
对于上面的转换关系做下说明:char可以自动转换成int,byte可以自动转换成short,short可以自动转换成int。这些转换过程都是自动完成的。
short a = 3;
int b = a;
那么将int赋值给short,或者将double赋值给int可不可以呢?答案是可以的,但是最后的数据可能不是我们预期的值。因为int占的字节数比short长,short盛放不下,会做数据截取。
- 整数之间的强制转换:比如int转换为short会缩短位数;
- double向int转换:会截掉小数部分。
3. 表达式类型自动提升
整个表达式的类型会提升为表达式中最高等级的类型。
short aa = 1;
//这个表达式会报错,aa+2会自动提升为int
short bb = aa+2;
包装类类型
包装类型是相对于基本数据类型来讲的。Java中有8种基本数据类型,每个基本数据类型都有相对的包装类型。比如int的包装类型是Integer。包装类型都是是引用类型,而且都是不可变类。
关于包装类型,有两个概念需要我们了解:
- 自动装箱:将基本数据类型赋值给包装类型的过程,实现原理是编译器层面new了一个包装类再赋值给相应的变量(如果是是将包装类缓存过的数值赋值给包装类的化,不会new新的包装了,而是直接使用缓存的对象);
- 自动拆箱:包装类型直接赋值给基本数据类型,实现原理类似,也是编译器层面调用了包装类的getValue方法再赋值给对应的基本数值类型。
比较有趣的是,包装类型在加载的过程中都会缓存某些值的类。比如Integer会缓存-128到127的类。
//不会使用缓存的数据
Integer num1 = new Integer(1);
//会使用缓存的数据,实际上是调用了Integer.valueOf方法
Integer num2 = 1;
通过设置-XX:AutoBoxCacheMax=?这个参数,可以调整Integer缓存的最大值。当然其他包装类型也有类似的行为,Byte、Short和Long都缓存了-128到127的类,Character缓存了0到127的类,但是这些类不能像Integer那样修改缓存的最大值。
关于基本数据类型和包装类型的使用,介绍一个阿里巴巴开发规范中的原则:
所有的POJO类属性必须使用包装数据类型;RPC方法的返回值和参数必须使用包装类型;所有的局部变量尽量使用基本数据类型。
定义布尔型的类变量,变量名不要以is
打头。
Java中的值传递和引用传递
基本数据类型,保存的是数据本身的值,按值传递;引用类型的变量保存的是对象在内存中的地址,按引用传递(引用传递可以看成是一种特殊的值传递)。
成员变量和方法作用域
对于成员变量和方法的作用域,有下面四种:
-
public : 表明该成员变量或者方法是对所有类或者对象都是可见的,所有类或者对象都可以直接访问;
-
private : 表明该成员变量或者方法是私有的,只有当前类对其具有访问权限,除此之外其他类或者对象都没有访问权限.子类也没有访问权限;
-
protected : 表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类;
-
default : 表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类。
一个问题
Java中的bool变量到底占用多大的内存呢?网上的各种说法都有?欢迎大家留言讨论。
公众号推荐
欢迎大家关注我的微信公众号「程序员自由之路」