Java是一门面向对象程序设计语言,和C++比起来,Java的使用难度与学习成本都比C++好上很多,但是如果你想入门Java这门语言很容易,但是一旦你要精通这门语言将是非常困难的一件事。Java的开源性与易用性使得越来越多的企业与个人利用Java语言实现各种各样的功能。现如今,Java可以运用来桌面级应用程序,嵌入式应用,web应用,分布式应用的编写,尤其在web应用程序的领域,J2EE体系规范几乎成为了其主流。
JRE与JDK
很多刚入门Java的开发者们会要配置Java的编程环境,此时他们会接触到JRE以及JDK,那么什么是JRE什么又是JDK?
JRE(Java Runtime Environment):JRE中文名叫Java运行环境,大家都知道Java程序是要依托于虚拟机运行的,Java虚拟机(JVM)正是集成在JRE中,同时JRE里还有一些Java的核心类库。
JDK(Java Development Kit):JDK中文名叫Java开发包,从名字上可以看出,JDK是专为Java开发人员所提供的。JDK中不光拥有JRE所有的功能,同时还具有编译器,调试器与其他一些开发小工具供开发者使用。*ps:在Java8之前,JRE以及JDK是独立分开的,因此配置Java编程环境时需要都进行配置,而在Java8之后,JDK中已经集成有JRE,可根据用户需求自动生成,因此JRE不需要开发者们进行单独配置。
简单来说:JVM⊆JRE⊆JDK
OpenJDK
OpenJDK可以说是Oracle JDK的阉割版本,是一个完全开源的版本。OpenJDK实现了最轻量JDK,摒弃了许许多多的开发小工具(如javafx等)。由于OpenJDK是完全开源的,因此在Oracle JDK中的一些涉及机密与知识产权的部分会被其他代码所代替。并且OpenJDK是不含有部署功能。
Java数据类型
基本类型
Java的基本数据类型一共有8种:
boolean类型按照使用规范的不同有不同的存储大小。
变量的一般初始化方法:
int a = 3;
常量的一般初始化方法:
final int a = 3;
变量和常量作用域一般就是其包含的整个函数体(从变量声明后算起)。
基本类型的转换
Java基本数据类型有2中转换方式,分别是强制类型转换与自动转换。
自动转换需要满足条件,也就是说一个取值范围小的数据类型和一个取值范围大的数据类型进行混合运算,那么我们Java会自动将取值范围小的那个数据转换成取值范围大的类型进行运算。
强制类型转换与自动转换相反,大多数情况是一个取值范围较大的数据类型向取值范围较小的数据类型进行转变的过程,其需要开发者进行操作。但是若强制转换的类型超过其转换后类型的取值范围,那么会导致溢出。
引用数据类型
Java的引用数据类型一般分为三种,分别是类(class),接口(interface)和数组(array),下面会分别为这3种类型进行举例分析:
(1)类(class)
该引用数据类型可以是用户自定义创建的,也可以是系统预定义的。比如说我们常用的实体类(Entity)就是一种引用数据类型,如:
public class Student { private String s_name; private String s_number; public Student(String s_name, String s_number) { this.s_name = s_name; this.s_number = s_number; } public String getS_name() { return s_name; } public void setS_name(String s_name) { this.s_name = s_name; } public String getS_number() { return s_number; } public void setS_number(String s_number) { this.s_number = s_number; }}
还有许多引用数据类型是系统预定义的,比如说我们的包装数据类型。
基本类型对于包装类型如下所示:
基本数据类型和引用包装类型有很大的不同,基本数据类型都有各自的初值,而引用与其对应的包装类型若不初始化其一律为null。基本数据类型变量存储在栈(stack)中,而引用包装类型将其地址指针存储在栈中,对象信息保存在堆(Heap)中。
(2)接口(interface)
同样该引用类型用户也可以自定义,且系统库中也有多种预定义接口。Java库中预定义接口有我们熟知的集合类List<>,Map等,具体会在Java集合框架一章中详细阐述。
(3)数组(array)
数组是一种数据结构,用来存储同一类型值的集合。数组的一般定义方式如下:
int[] array = new int[10];
或
int array[] = new int[10];
上述定义方式为数组的动态初始化方式,还有一种数组静态初始化方式如下:
int[] array = new int[]{1,2,3,4,5};
要注意,我们在初始化数组长度的时候是可以定义长度为0的数组,比如说一个方法的返回值是一个数组,当碰巧返回结果为空时可以用长度为0的数组表示,但是长度为0的数组和null不同。
大家都知道java的main函数中都带一个String args[]的参数,为什么要这么做呢?其实main函数中每个关键字每个部分都是非常有说法的,下面就和大家来说说main函数。
首先main函数一定要用public修饰,这是因为我们Java运行程序是基于JVM的,JVM在运行Java程序的时候会先找main函数,因此需要设置public权限使得JVM可以正确找到程序入口。static关键字说明我们main函数是一个静态方法,这是由于JVM在找到main函数后不会直接加载其公共类,而是直接运行这个main方法,因此需要static修饰。将main函数设为void是由于JVM无法处理有返回值的main函数,这是一种规定。而main方法中字符串参数数组是为了接收命令行输入参数,使得开发者或使用者可以利用命令行和程序进行交互。
Java运算符
算数运算符
算数运算符一般是用在数学的表达式中,和我们日常理解的数学表达式中的应用一致,具体有:+(加)-(减)*(乘法)/(除)%(取余mod)
算数运算符中也有二元运算符,如
int a + = 3; // 相当于int a = a + 3;
自增自减运算符
分别有2种,一种是前缀的自增自减,一种是后缀的自增自减。
前缀的自增自减先进行自增自减运算再进行表达式的运算,如:
int a = ++b;// b = b + 1;a = b;
后缀的自增自减先进行表达式的运算再进行自增自减运算,如:
int a = b++;//a = b;b = b + 1;
关系运算符
关系运算符用于比较两个数值的大小或者相等性,如:==(等于)!=(不等于)>(大于)=(大于等于)<=(小于等于)。
关系运算符的运算结果一般返回是一个boolean类型的数值,如:
int a = 3;a == 3;//结果为true
逻辑运算符
逻辑运算符一般是用来逻辑运算式的判断,其运算数据和运算结果都为boolean类型,如:&&(与)||(或)!(非)^(异或)。
使用方法如下:
boolean a = true;boolean b = false;a || b;//结果为true
位运算符
位运算符是以二进制数进行逐位运算,其运算数据以及运算结果都为int类型,如**&(与)|(或)~(非)^(异或)>>(右移)<
使用方法如下:
int a = 1;//a = 0b00000001int b = 2;//b = 0b00000010Int c = a | b;//c = 0b00000001 | 0b00000010 = 0b00000011 = 3
运算符优先级
Java的表达式中运算符都有各自的优先级,如下表所示,优先级从上至下递减:
枚举类
当有一个变量取值在一个有限的集合内,可以用枚举类进行表示,如:
enum Sex{MALE,FEMALE};
假如你这时候要使用这个枚举类,可以如下使用:
student.getSex(Sex.MALE);
Java字符串
String字符串声明方法
我们用String来定义一个字符串一般使用两种方法:
(1)声明字符串常量:
String str = “Hello”;
该种声明方式使得字符串常量直接在.class文件加载进入内存之后就写入JVM方法区的常量池中,常量池就会给该字符串一个地址,该字符串的地址指针则存储来栈中,当程序要调用这个字符串时,栈中的指针指向存储在常量池中的需要引用的字符串获取其内容。如果第二次声明字符串使用了相同的字符串,那么JVM就不会再给这个相同的字符串分配一个地址,还是使用该字符串的原地址,但是新字符串的地址指针还是会在栈中分配。
(2)声明字符串对象:
String str = new String(“Hello”);
该种声明方式由于是新建了String这个对象,而我们对象的信息是存储在JVM的堆中。如果此时我们声明了多个相同内容的字符串对象,那么在JVM的堆中同样也会生成相同的数量的对象,在Java栈中也生成相同数量的地址指针。所以说使用字符串对象声明字符串的话在内存中的表现形式是1对1的形式。
String字符串类常用方法
(1)获取字符串长度String.length():
String str = “Hello”;int a = str.length();//结果为a = 5
(2)获取某一位置字符String.CharAt(int index):
String str = “Hello”;char a = str.CharAt(3);//结果为a = l
(3)返回字符串出现的位置String.indexOf(String str,int index):
String str = “Hello”;int a = str.indexOf(“o”,2);//结果为 a = 4
(4)替换字符串的部分形成新字符串String.replace(char old,char new);
String str = “Hello”;String newStr = str.replace(“o”,”oWorld”);//结果为newStr = HelloWorld
该方法只能替换第一次出现的字符,若全部的字符都要替换则使用replaceAll()方法即可。
(5)转换字符串方法Object.toString();
Integer num = 1;String str = num.toString();//结果为str = 1;
字符串比较
(1)以Unicode值为标准比较字符串大小String.compareTo(String str):
String str1 = “Hello”;String str2 = new String(“Hello”);boolean a = str1.equals(str2);//结果为a = true
该比较方法规定当字符串Unicode值小于参数字符串Unicode值的时候,结果返回负值;若字符串Unicode值等于参数字符串Unicode值的时候,结果返回0;若字符串Unicode值大于参数字符串Unicode值的时候,则结果返回一个正数。
上述比对方法是没有忽略字符串的大小,若要忽略字符串大小可以使用用String.compareToIgnoreCase(String str)方法。
(2)比较两个字符串是否相等String.equals(String str):
String str1 = “Hello”;String str2 = new String(“Hello”);boolean a = str1.equals(str2);//结果为a = true
该方法首先比较字符串的大小,之后比对字符串的Unicode值是否完全相等,若完全相等,则返回true,否则返回false。
若需要忽略字符串大小进行判断的话需要使用String.equalsIgnoreCase(String str)。
我们一般不使用 == 进行字符串的比对,因为 == 只会比对对象的内存地址,由于我在上文中叙述的两类字符串声明方法有各自的特殊之处,因此使用“==”会造成许多错误,下面将会举一个例子深度讲解一下:
public class Test { public static void main(String[] args){ String str1 = "Hello"; String str2 = "Hello"; String str3 = new String("Hello"); String str4 = new String("Hello"); System.out.println(str1 == str2 ? true : false); System.out.println(str1 == str3 ? true : false); System.out.println(str3 == str4 ? true : false); System.out.println(str1.equals(str2)); System.out.println(str1.equals(str3)); System.out.println(str3.equals(str4)); }}
结果为:
truefalsefalsetruetruetrue
之前有说过重复创建相同字符串的话在常量池中会公用一个地址,所以用 == 判断的结果是true,因为 == 只会判断对象的内存地址,这就是第二第三条语句判断返回false的原因,因为字符串常量在常量池中的地址与字符串对象在堆中的地址不一样。而之后用String.equals(String str)方法进行比对都返回true,因为该方法先比对了其大小,后比对其Unicode值是否相等,并没有比对其在内存中的地址。
空串与NULL
空串与NULL是两个不同的概念。空串是一个长度为0的字符串,可以用String.length() == 0 或者用String.equals(“”)进行判断;null的话直接可以用str == null进行判断。
如果即要判断空串也要判断null的话可以使用StringUtils工具类中的StringUtils.isEmpty(String str)或者StringUtils.isBlank(String str)两种方法。
StringUtils.isEmpty(String str)与StringUtils.isBlank(String str)的区别:StringUtils.isEmpty(String str)是判断字符串长度是否为0,或者是否为空串;StringUtils.isBlank(String str)是判断字符串长度是否为0,是否为空串,是否有空白字符串组成。
ps:null对象如果调用String.equals(String str)方法会抛出NPE(空指针异常)。
可变字符序列
由于String字符串是用final修饰的,因此无法进行继承以及修改。因此Java API给予了开发者一类可变的字符序列StringBuffer以及StringBuilder。虽然两者都是可变的,但是其线程安全性以及运行效率是不同的,可以根据实际运用的场合灵活变通。
(1)运算速度比较:String < StringBuffer < StringBuilder
由于String是字符串常量,因此String对象的每次改变相当于重新生成一个新的对象,JVM内部就会不断地进行对象的销毁以及创建,故执行速度最慢。但是在特殊情况下(JDK1.5之后),String字符串的拼接操作会在JVM中自动转换为StringBuffer的对象进行操作,故只能说在一般情况下String执行速度最慢。
而StringBuffer与StringBuilder都是集成与同一个抽象类AbstractStringBuilder,且两者的具体实现方法相类似,但是StringBuffer有Synchronized约束,要考虑到各种并发操作的情况,故运行起来是比较耗时的,相比如StringBuffer,StringBuilder没有Synchronized约束,故执行速度较快。
(2)线程安全性比较:StringBuffer安全,String与StringBuilder不安全
StringBuffer是JDK1.0版本加入的,由于其中大部分方法利用Synchronized约束,实现了线程的同步,因此可以将StringBuffer看作是一个顺序的执行过程,并且是线程安全的。而StringBuilder是在JDK1.5版本加入的,为了弥补在某些情况下StringBuffer运行效率较慢的问题。StringBuilder没有对线程的并发操作进行约束,因此是线程不安全的,并且在很多情况下其执行速度是大大快于StringBuffer,因此你可以将其看做是一个轻量级的StringBuffer。
所以如果要执行少量的操作,可以使用String字符串;如果要在单线程下执行的操作较多,推荐使用StringBuilder;而要在多线程并发的情况下执行大量的字符串操作,推荐使用StringBuffer。
Java输入输出
标准输入输出流
所谓标准的输入输出流,就是在控制台(console)中控制输入和输出。
在著名的HelloWorld程序中就已经用到了Java的标准输出流:
System.out.println(“Hello World!”);
在这句代码中我们是调用了System类中的静态PrintStream类型的成员out。而println()方法是PrintStream类中的一个方法,它用作在控制台中打印信息。PrintStream.println()在打印信息后自动换行,而PrintStream.print()方法则在打印之后不会执行其他操作。
标准的输入流需要创建一个Scanner对象进行操作。Scanner是一个简单的文本扫描器。首先我们需要将Java的标准输入流与Scanner对象进行绑定:
Scanner scan = new Scanner(System.in);
之后将输入的输入流保存在一个字符串中:
String str = scan.nextLine();
如果需要输入不同的内容可以调用不同的方法进行实现:
(1)Scanner.next():读取输入的下一个字符串,遇到空格等视作结束
(2)Scanner.nextLine():读取输入的下一个字符串,遇到空格等不视为结束。
(3)Scanner.nextInt():读取输入的下一个整形数据。
(4)Scanner.nextFloat():读取输入的下一个浮点型数。
更多有关Java输入输出(I/O)将在Java I/O一章详细叙述。
Java控制流程
条件分支语句
一般写法:
if(condition1) {//TODO STH} else if(condition2) {//TODO STH}else {//TODO STH}//condition为布尔表达式
条件语句用在判断选择执行的情况。
循环语句
(1)for循环:
一般写法:
for(int i = 0; i < 10; i ++) {//TODO STH}
for循环用于确定循环次数的循环。
(2)while循环
一般写法:
while(condition){//TODO STH}//condition为布尔表达式
(3)do-while循环
一般写法:
do{//TODO STH} while(condition);//condition为布尔表达式
(4)增强for循环
一般写法:
for(数值类型 : 循环对象){//TODO STH}
增强for循环实现了对数组或是集合的遍历。
多重选择
当需要处理多个分支的情况,使用传统的条件分支语句会使编码看起来比较复杂繁琐,因此Java提供了多重选择switch语句来实现多分支选择。
一般写法:
switch(表达式){case 表达式结果1://TODO STHbreak;case 表达式结果2://TODO STHbreak;....default://TODO STH}多重选择语句会根据switch表达式的不同选取与之相对应case执行,如果switch表达式没有在case所罗列的结果中出现,则会跳转进入default执行。
多重选择语句会根据switch表达式的不同选取与之相对应case执行,如果switch表达式没有在case所罗列的结果中出现,则会跳转进入default执行。
中断控制流程语句
Java语言的设计者将goto作为保留字,但是没有使用goto这个语句。由于多年来的经验许许多多开发者认为goto语句是造成程序崩溃的最大原因之一,因此近年来许许多多编程语言都不建议开发者使用goto语句。但是在某些情况下使用goto可以使程序变得更可控逻辑实现更简单,因此Java语言设计者添加了break以及continue语句来实现这种效果。
Break
使用break语句可以直接跳出当前的循环体,其一般写法如下:
while(condition1){if(condition2){break;}}
如果满足condition2表达式的条件,则直接跳出当前while循环。
Continue
使用continue语句可以终止当前次的循环,开始下一次循环,其一般写法如下:
while(condition1){if(condition2){Continue;}}
若满足condition2表达式的条件,则直接终止当前此的循环开始下一次的循环。
本文参考Core Java一书,并且结合博主的知识心得进行拓展,如有错误之处望大家指正!