为什么Java应用最广泛?
从互联网到企业平台,Java是应用最广泛的编程语言,原因在于:
- Java是基于JVM虚拟机的跨平台语言,一次编写,到处运行;
- Java程序易于编写,而且有内置垃圾收集,不必考虑内存管理;
- Java虚拟机拥有工业级的稳定性和高度优化的性能,且经过了长时期的考验;
- Java拥有最广泛的开源社区支持,各种高质量组件随时可用。
Java语言常年霸占着三大市场:
- 互联网和企业应用,这是Java EE的长期优势和市场地位;
- 大数据平台,主要有Hadoop、Spark、Flink等,他们都是Java或Scala(一种运行于JVM的编程语言)开发的;
- Android移动平台。
这意味着Java拥有最广泛的就业市场。
Java最早是由SUN公司(已被Oracle收购)的詹姆斯·高斯林(高司令,人称Java之父)在上个世纪90年代初开发的一种编程语言,最初被命名为Oak,随着互联网的崛起Oak重新焕发生机。在1995年以Java的名称正式发布,原因是Oak已经被人注册了,因此SUN注册了Java这个商标。
Java介于编译型语言和解释型语言之间。Java是将代码编译成一种字节码,它类似于抽象的CPU指令,然后,针对不同平台编写虚拟机,不同平台的虚拟机负责加载字节码并执行,这样就实现了一次编写,到处运行的效果。当然,这是针对Java开发者而言。对于虚拟机,需要为每个平台分别开发。为了保证不同平台、不同公司开发的虚拟机都能正确执行Java字节码,SUN公司制定了一系列的Java虚拟机规范。从实践的角度看,JVM的兼容性做得非常好,低版本的Java字节码完全可以正常运行在高版本的JVM上。
随着Java的发展,SUN给Java又分出了三个不同版本:
- Java SE:Standard Edition
- Java EE:Enterprise Edition
- Java ME:Micro Edition
- 这三者的关系如下图所示:
简单来说,Java SE就是标准版,包含标准的JVM和标准库,而Java EE是企业版,它只是在Java SE的基础上加上了大量的API和库,以便方便开发Web应用、数据库、消息服务等,Java EE的应用使用的虚拟机和Java SE完全相同。
Java ME就和Java SE不同,它是一个针对嵌入式设备的瘦身版,Java SE的标准库无法在Java ME上使用,Java ME的虚拟机也是瘦身版。
因此我们推荐的Java学习路线图如下:
- 首先要学习Java SE,掌握Java语言本身、Java核心开发技术以及Java标准库的使用;
- 如果继续学习Java EE,那么Spring框架、数据库开发、分布式架构就是需要学习的;
- 如果要学习大数据开发,那么Hadoop、Spark、Flink这些大数据平台就是需要学习的,他们都基于Java或Scala开发;
- 如果想要学习移动开发,那么就深入Android平台,掌握Android App开发。
无论怎么选择,Java SE的核心技术是基础,这个教程的目的就是让你完全精通Java SE!
从1995年发布1.0版本开始,到目前为止,最新的Java版本是Java 15:
名词解释
初学者学Java,经常听到JDK、JRE这些名词,它们到底是啥?
- JDK:Java Development Kit
- JRE:Java Runtime Environment
简单地说,JRE就是运行Java字节码的虚拟机。但是,如果只有Java源码,要编译成Java字节码,就需要JDK,因为JDK除了包含JRE,还提供了编译器、调试器等开发工具。
二者关系如下:
- JSR规范:Java Specification Request
- JCP组织:Java Community Process
变量和数据类型
我们先讨论基本类型的变量。Java提供了两种变量类型:基本类型和引用类型
在Java中,变量必须先定义后使用,在定义变量的时候,可以给它一个初始值。例如:
intx = 1;
我们一行一行地分析代码执行流程:
执行int n = 100;
,该语句定义了变量n
,同时赋值为100
,因此,JVM在内存中为变量n
分配一个存储单元,填入值100
:
执行n = 200;
时,JVM把200
写入变量n
的存储单元,因此,原有的值被覆盖,现在n
的值为200
:
执行int x = n;
时,定义了一个新的变量x
,同时对x
赋值,因此,JVM需要新分配一个存储单元给变量x
,并写入和变量n
一样的值,结果是变量x
的值也变为200
:
执行x = x + 100;
时,JVM首先计算等式右边的值x + 100
,结果为300
(因为此刻x
的值为200
),然后,将结果300
写入x
的存储单元,因此,变量x
最终的值变为300
:
可见,变量可以反复赋值。注意,等号=
是赋值语句,不是数学意义上的相等,否则无法解释x = x + 100
。
基本数据类型
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
计算机内存的最小存储单元是字节(byte),一个字节就是一个8位二进制数,即8个bit。它的二进制表示范围从00000000
~11111111
,换算成十进制是0~255,换算成十六进制是00
~ff
。
内存单元从0开始编号,称为内存地址。每个内存单元可以看作一间房间,内存地址就是门牌号。
一个字节是1byte,1024字节是1K,1024K是1M,1024M是1G,1024G是1T。
不同的数据类型占用的字节数不一样。我们看一下Java基本数据类型占用的字节数:
byte
恰好就是一个字节,而long
和double
需要8个字节。
也可以将结果强制转型,即将大范围的整数转型为小范围的整数。强制转型使用(类型)
,例如,将int
强制转型为short
:
inti = 12345;shorts = (short) i;// 12345
浮点数常常无法精确表示,并且浮点数的运算结果可能有误差;
比较两个浮点数通常比较它们的绝对值之差是否小于一个特定值;
整型和浮点型运算时,整型会自动提升为浮点型;
可以将浮点型强制转为整型,但超出范围后将始终返回整型的最大值。
Java的编译器对字符串做了特殊照顾,可以使用+
连接任意字符串和其他数据类型,这样极大地方便了字符串的处理。
Java的字符类型char
是基本类型,字符串类型String
是引用类型;
基本类型的变量是持有某个数值,引用类型的变量是指向某个对象;
引用类型的变量可以是空值null
;
要区分空值null
和空字符串""
。
流程控制
输出:
在前面的代码中,我们总是使用System.out.println()
来向屏幕输出一些内容。
println
是print line的缩写,表示输出并换行。因此,如果输出后不想换行,可以用print()
:
浮点数的比较不能用等号,因为有精度问题:
publicclassMain{publicstaticvoidmain(String[]args){doublex=1-9.0/10;if(Math.abs(x-0.1)<0.00001){System.out.println("x is 0.1");}else{System.out.println("x is NOT 0.1");}}}
判断引用类型相等
在Java中,判断值类型的变量是否相等,可以使用==
运算符。但是,判断引用类型的变量是否相等,==
表示引用是否相等,或者说,是否指向同一个对象。例如,下面的两个String类型,它们的内容是相同的,但是,分别指向不同的对象,用==
判断,结果为false
:
要判断引用类型的变量内容是否相等,必须使用equals()
方法
switch表达式
使用switch
时,如果遗漏了break
,就会造成严重的逻辑错误,而且不易在源代码中发现错误。从Java 12开始,switch
语句升级为更简洁的表达式语法,使用类似模式匹配(Pattern Matching)的方法,保证只有一种路径会被执行,并且不需要break
语句:
publicclassMain{publicstaticvoidmain(String[]args){Stringfruit="apple";switch(fruit){case"apple"->System.out.println("Selected apple");case"pear"->System.out.println("Selected pear");case"mango"->{System.out.println("Selected mango");System.out.println("Good choice!");}default->System.out.println("No fruit selected");}}}
yield
大多数时候,在switch
表达式内部,我们会返回简单的值。
但是,如果需要复杂的语句,我们也可以写很多语句,放到{...}
里,然后,用yield
返回一个值作为switch
语句的返回值:
publicclassMain{publicstaticvoidmain(String[]args){Stringfruit="orange";intopt=switch(fruit){case"apple"->1;case"pear","mango"->2;default->{intcode=fruit.hashCode();yieldcode;// switch语句返回值}};System.out.println("opt = "+opt);}}
表面上看,上面的while
循环是一个死循环,但是,Java的int
类型有最大值,达到最大值后,再加1会变成负数,结果,意外退出了while
循环。
但是,很多时候,我们实际上真正想要访问的是数组每个元素的值。Java还提供了另一种for each
循环,它可以更简单地遍历数组:
publicclassMain{publicstaticvoidmain(String[]args){int[]ns={1,4,9,16,25};for(intn:ns){System.out.println(n);}}}
遍历数组for
publicclassMain{publicstaticvoidmain(String[]args){int[]ns={1,4,9,16,25};for(inti=0;i<ns.length;i++){intn=ns[i];System.out.println(n);}}}
遍历数组foreach
publicclassMain{publicstaticvoidmain(String[]args){int[]ns={1,4,9,16,25};for(intn:ns){System.out.println(n);}}}
注意:在for (int n : ns)
循环中,变量n
直接拿到ns
数组的元素,而不是索引。
显然for each
循环更加简洁。但是,foreach
循环无法拿到数组的索引,因此,到底用哪一种for
循环,取决于我们的需要。
数组排序
冒泡排序
import java.util.Arrays;
publicclassMain{publicstaticvoidmain(String[]args){int[]ns={28,12,89,73,65,18,96,50,8,36};// 排序前:System.out.println(Arrays.toString(ns));for(inti=0;i<ns.length-1;i++){for(intj=0;j<ns.length-i-1;j++){if(ns[j]>ns[j+1]){// 交换ns[j]和ns[j+1]:inttmp=ns[j];ns[j]=ns[j+1];ns[j+1]=tmp;}}}// 排序后:System.out.println(Arrays.toString(ns));}}冒泡排序的特点是,每一轮循环后,最大的一个数被交换到末尾,因此,下一轮循环就可以刨除最后的数,每一轮循环都比上一轮循环的结束位置靠前一位。另外,注意到交换两个变量的值必须借助一个临时变量。像这么写是错误的
二维数组
importjava.util.Arrays;publicclassMain{publicstaticvoidmain(String[]args){int[][]ns={{1,2,3,4},{5,6,7,8},{9,10,11,12}};System.out.println(Arrays.deepToString(ns));}}
三维数组
int[][][]ns={{{1,2,3},{4,5,6},{7,8,9}},{{10,11},{12,13}},{{14,15,16},{17,18}}};
二维数组就是数组的数组,三维数组就是二维数组的数组;
多维数组的每个数组元素长度都不要求相同;
打印多维数组可以使用Arrays.deepToString()
;
最常见的多维数组是二维数组,访问二维数组的一个元素使用array[row][col]
。
面向对象编程
定义class
在Java中,创建一个类,例如,给这个类命名为Person
,就是定义一个class
:
classPerson{publicStringname;publicintage;}
一个class
可以包含多个字段(field
),字段用来描述一个类的特征。上面的Person
类,我们定义了两个字段,一个是String
类型的字段,命名为name
,一个是int
类型的字段,命名为age
。因此,通过class
,把一组数据汇集到一个对象上,实现了数据封装。
public
是用来修饰字段的,它表示这个字段可以被外部访问。
创建实例
Personming=newPerson();
上述代码创建了一个Person类型的实例,并通过变量ming
指向它。
注意区分Person ming
是定义Person
类型的变量ming
,而new Person()
是创建Person
实例。
有了指向这个实例的变量,我们就可以通过这个变量来操作实例。访问实例变量可以用变量.字段
,例如:
ming.name="Xiao Ming";// 对字段name赋值ming.age=12;// 对字段age赋值System.out.println(ming.name);// 访问字段namePersonhong=newPerson();hong.name="Xiao Hong";hong.age=15;
把field
从public
改成private
,外部代码不能访问这些field
,那我们定义这些field
有什么用?怎么才能给它赋值?怎么才能读取它的值?
所以我们需要使用方法(method
)来让外部代码可以间接修改field
:
publicclassMain{publicstaticvoidmain(String[]args){Personming=newPerson();ming.setName("Xiao Ming");// 设置nameming.setAge(12);// 设置ageSystem.out.println(ming.getName()+", "+ming.getAge());}}
classPerson{privateStringname;privateintage;publicStringgetName(){returnthis.name;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnthis.age;}publicvoidsetAge(intage){if(age<0||age>100){thrownewIllegalArgumentException("invalid age value");}this.age=age;}}
虽然外部代码不能直接修改private
字段,但是,外部代码可以调用方法setName()
和setAge()
来间接修改private
字段。在方法内部,我们就有机会检查参数对不对。比如,setAge()
就会检查传入的参数,参数超出了范围,直接报错。这样,外部代码就没有任何机会把age
设置成不合理的值。
对setName()
方法同样可以做检查,例如,不允许传入null
和空字符串:
publicvoidsetName(Stringname){if(name==null||name.isBlank()){thrownewIllegalArgumentException("invalid name");}this.name=name.strip();// 去掉首尾空格}
构造方法
创建实例的时候,实际上是通过构造方法来初始化实例的。我们先来定义一个构造方法,能在创建Person
实例的时候,一次性传入name
和age
,完成初始化:
publicclassMain{publicstaticvoidmain(String[]args){Personp=newPerson("Xiao Ming",15);System.out.println(p.getName());System.out.println(p.getAge());}}classPerson{privateStringname;privateintage;publicPerson(Stringname,intage){this.name=name;this.age=age;}publicStringgetName(){returnthis.name;}publicintgetAge(){returnthis.age;}}
由于构造方法是如此特殊,所以构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void
),调用构造方法,必须用new
操作符。
如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来:
// 构造方法
publicclassMain{publicstaticvoidmain(String[]args){Personp1=newPerson("Xiao Ming",15);// 既可以调用带参数的构造方法Personp2=newPerson();// 也可以调用无参数构造方法}}classPerson{privateStringname;privateintage;publicPerson(){}publicPerson(Stringname,intage){this.name=name;this.age=age;}publicStringgetName(){returnthis.name;}publicintgetAge(){returnthis.age;}}
没有在构造方法中初始化字段时,引用类型的字段默认是null
,数值类型的字段用默认值,int
类型默认值是0
,布尔类型默认值是false
:
方法重载
在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello
类中,定义多个hello()
方法:
classHello{publicvoidhello(){System.out.println("Hello, world!");}publicvoidhello(Stringname){System.out.println("Hello, "+name+"!");}publicvoidhello(Stringname,intage){if(age<18){System.out.println("Hi, "+name+"!");}else{System.out.println("Hello, "+name+"!");}}}
这种方法名相同,但各自的参数不同,称为方法重载(Overload
)。重载方法返回值类型应该相同。
继承
//person类classPerson{privateStringname;privateintage;publicStringgetName(){...}publicvoidsetName(Stringname){...}publicintgetAge(){...}publicvoidsetAge(intage){...}}
现在,假设需要定义一个Student
类,字段如下:
classStudent{privateStringname;privateintage;privateintscore;publicStringgetName(){...}publicvoidsetName(Stringname){...}publicintgetAge(){...}publicvoidsetAge(intage){...}publicintgetScore(){…}publicvoidsetScore(intscore){…}}
继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让Student
从Person
继承时,Student
就获得了Person
的所有功能,我们只需要为Student
编写新增的功能。
注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object
特殊,它没有父类。
protected
继承有个特点,就是子类无法访问父类的private
字段或者private
方法。例如,Student
类就无法访问Person
类的name
和age
字段:
这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private
改为protected
。用protected
修饰的字段可以被子类访问:
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容