3小时Java入门

2020-07-20 15:34:40 浏览数 (1)

最近狂写了一个月的Spark,接手的项目里的代码以Scala语言为主,Java为辅,两种语言混合编码。发现要深入地掌握Scala,很有必要学习一下Java,以便理解JVM语言的编译执行和打包机制,并通过对比加深对Scala的静态语言和脚本语言双重特性的理解。

参考了廖雪峰老师的Java教程,以及网络上的一些其他博客,对比 《3小时Python入门》和《3小时Scala入门》的整体框架,大概投入了50个小时的学习和整理,于是就有了本篇文章。写完本篇文章后,又回去调了一下项目代码,一些棘手的包依赖的问题都最终获得了解决。用Scala写起Spark来更加感到如丝般顺滑。

〇,编程环境

工程项目推荐使用IDEA.

入门学习推荐使用jupyter notebook.

安装jupyter notebook的java插件 IJava 的方法如下:

1,下载Java JDK >= 9.建议12

2,下载ijava-1.3.0.zip,并解压。

3,进入解压后目录,运行 python3 install.py --sys-prefix。

详情参见:https://github.com/SpencerPark/IJava

也可以在以下网页链接中直接尝试IJava:

https://mybinder.org/v2/gh/SpencerPark/ijava-binder/master

一,算数运算

二,输入输出

输入:System.in, java.io.InputStreamReader, java.util.Scanner

输出:System.out.println, System.out.printf, System.out.print

读文件:java.io.FileInputStream

写文件:java.io.FileOutputStream

1,输入

Scanner扫描输入,遇到n结束。

BufferedReader.read() 逐字符读取。

BufferedReader.readLine() 逐行读取。

2,输出

print不换行,println换行,printf格式化输出。

3,读文件

4,写文件

三,导入Package

java有以下一些导入包的方式:

1,导入包中某个对象:import java.text.SimpleDateFormat

2,导入包中全部对象: import java.util.*

3,导入包中的静态字段和方法(较少使用): import static java.lang.System.*

4,默认导入: java默认导入了java.lang.*

四,语法规则

1,标识符

标识符由字母和数字组成,遵循驼峰命名规则。

类的名称以大写字母开头。

方法的名称以小写字母开头。

变量的名称以小写字母开头。

2,注释

单行注释用//开头。

多行注释用/*开头,以*/结尾。

特殊多行注释,以/**开头,以*/结束,如果有多行,每行通常以星号开头。

这种特殊的多行注释需要写在类和方法的定义处,可以用于自动创建文档。

3,数据类型

Java 的数据类型有两大类,基本数据类型和引用数据类型。

基本数据类型相对非常底层,基本类型相同值的对象占有同样的存储单元,判断是否相等可以用 ==。

引用数据类型本质上都是Class,相对抽象,引用类型相同值的对象占用不同的存储单元,判断是否相等应该用 equals方法。

基本数据类型包括:整型(byte,short,int,long),浮点型(float,double),布尔类型(boolean),字符类型(char)

引用数据类型包括:包装类型(Integer,Double,Char,Boolean,……),字符串(String),数组(Array),以及各种容器类(List,Map,Set,Queue)。

用户自定义的任何Class都可以看成是一种引用数据类型。

4,变量声明

5,标点符号

Java 中常用的标点符号用法总结如下

  • ()表示优先级或者函数参数列表
  • []用于索引或数组声明
  • {}用于作用域
  • <>用于泛型
  • * 用于import包时的通配符
  • @用于注解

五,编译执行

1,程序结构

一个.java程序文件中必须有且只有一个public类,该类必须有一个声明为main函数作为程序入口。

并且这个main函数需要声明为 public static void 类型,即静态的,公开的,返回值为空的函数类型。

并且这个java程序的文件名必须和这个public类名保持一致。

将以上代码拷贝到文本文件中,命名为 Main.java。

2,编译执行

Java是一种解释型语言,其java源码需要被编译成class字节码运行在Java虚拟机上。

因此,执行Java程序分两步:

(1),使用javac编译命令将以.java结束的程序文件编译成以class结尾的字节码程序文件。

javac Main.java 编译后得到 Main.class文件

(2),使用java 命令运行字节码程序。

java -classpath ./ Main 在JVM上执行Main.class文件

编译时,按下面的顺序依次查找类:

(1)查找当前package是否存在这个class;

(2)查找import的包是否包含这个class;

(3)查找java.lang包是否包含这个class。

如果按照上面的规则还无法确定类名,则编译报错。

3,classpath和jar包

(1) classpath

classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。

它其实就是一组目录的集合,它设置的搜索路径与操作系统相关。

例如,在Windows系统上,用;分隔,可能长这样。

C:workproject1bin;C:shared;"D:My Documentsproject1bin"

在Linux系统上,用:分隔,可能长这样。

/usr/shared:/usr/local/bin:/home/liaoxuefeng/bin

如果JVM在某个路径下找到了对应的class文件,就不再往后继续搜索。如果所有路径下都没有找到,就报错。

classpath的设定方法有两种:

在系统环境变量中设置classpath环境变量,不推荐;

在启动JVM时设置classpath变量,推荐。

我们强烈不推荐在系统环境变量中设置classpath,那样会污染整个系统环境。在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath或-cp参数:

java -classpath .;C:workproject1bin;C:shared abc.xyz.Hello

但通常classpath这个参数不需要配置,其默认值为当前目录 ./一般就够用了。

(2) jar包

设想一下,如果有很多.class文件,散落在各层目录中,肯定不便于管理。

如果能把目录打一个包,变成一个文件,就方便多了。

jar包就是用来干这个事的,它可以把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件,这样一来,无论是备份,还是发给客户,就简单多了。

jar包实际上就是一个zip格式的压缩文件,而jar包相当于目录。如果我们要执行一个jar包的class,就可以把jar包放到classpath中:

java -cp ./hello.jar abc.xyz.Hello

jar包还可以包含一个特殊的/META-INF/MANIFEST.MF文件,MANIFEST.MF是纯文本,可以指定Main-Class和其它信息。JVM会自动读取这个MANIFEST.MF文件。如果存在Main-Class,我们就不必在命令行指定启动的类名,而是用更方便的命令:

java -jar hello.jar

jar包还可以包含其它jar包,这个时候,就需要在MANIFEST.MF文件里配置classpath了

4,maven项目管理工具

实际项目开发中,通常使用maven管理项目,并打成jar包。

maven使用POM文件POM.xml指定项目的依赖和打包方式。

maven安装后,将会在本地创建~/.m2/repository目录,集中存放jar包作为本地仓库。

maven搜索并载入依赖的顺序如下:本地仓库->私人远程仓库->中央仓库

常见的maven 命令如下:

  • mvn clean 清理编译打包输出
  • mvn compile 项目编译
  • mvn package 项目打包
  • mvn install 安装到本地仓库

六,Java数据结构概述

Java中常用的数据结构主要包括字符串(String),数组(Array),枚举(enum), 以及java.util中的各种容器类(通常被称做集合)。

java.util中的这些容器类分成两大类,一类是实现了Collection接口,另外一类实现了Map接口。

容器类中常用的数据结构包括:列表(List),映射(Map),集合(Set),队列(Quene),堆栈(Stack)。

当然这些数据结构也都是接口,通过API封装了特定的功能,下面还会有多种不同的实现。

可以用统一的Iterator方式对大多数容器类进行遍历,这种更加抽象的方式优于使用下标的方式进行遍历。

七,字符串String

Java 中的字符串和Scala中的字符串来源于同一个包,java.lang.String,两者具有完全相同的方法。

以下为字符串一些常用操作。

八,数组Array

Java 中的数组和 C 中的数组很像,其长度是不可变的,但是数组中的元素内容是可以改变的。

数组是引用类型,一般是用花括号{}作为数组范围标识。

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

1,创建Array

2,Array的常用操作

九,列表List

Java中的列表List是一种有序数据结构的接口。

它有两种实现,一种是ArrayList,另外一种是LinkedList。前者是顺序存储,方便查询和修改特定元素。后者是链表存储,方便插入和删除元素。通常情况下我们使用ArrayList更多一些。

和数组Array不同,List的大小是可以改变的。

List的主要方法如下:(E是元素 e的类型)

  • 在末尾添加一个元素:void add(E e)
  • 在指定索引添加一个元素:void add(int index, E e)
  • 删除指定索引的元素:int remove(int index)
  • 删除某个元素:int remove(Object e)
  • 获取指定索引的元素:E get(int index)
  • 获取列表大小(包含元素的个数):int size()

1,创建List

2,List常用操作

十,映射Map

Map是一种无序数据结构的接口,存储键值对(key,value)。

Map的常用实现是HashMap, LinkedHashMap, TreeMap。其中TreeMap是一种有序的Map.

Map的常用方法是put和get。如果想查询某个key是否存在,可以调用containsKey.

Map中的key是唯一的,作为key的对象必须实现equals和hashCode方法。使用TreeMap时,放入的Key必须实现Comparable接口。

Map通常用来高效地进行查找。

1,创建Map

2,Map常用操作

十一,集合Set

Set用于存储不重复的元素集合,它主要提供以下几个方法:

  • 将元素添加进Set<E>:boolean add(E e)
  • 将元素从Set<E>删除:boolean remove(Object e)
  • 判断是否包含元素:boolean contains(Object e)

十二,迭代器

Java的容器类都可以使用for each循环,List、Set和Queue会迭代每个元素,Map会迭代每个key。

下面以List和Set的for each遍历为例。

实际上,Java编译器并不知道如何遍历List和Set。

上述代码能够编译通过,只是因为编译器把for each循环通过Iterator改写为了普通的for循环:

Iterator是一种抽象的数据访问模型。使用Iterator模式进行迭代的好处有:

  • 对任何容器都采用同一种访问模型;
  • 调用者对容器内部结构一无所知;
  • 容器类返回的Iterator对象知道如何迭代。

如果我们自己编写了一个容器类,想要使用for each循环,则该容器类要实现Iterable接口,并返回一个Iterator对象,下面是一个范例。

十三,枚举类enum

如果有一些相关的常量,如星期,月份,颜色,可以将其它们定义为枚举类型。

枚举类型常用的方法有name和ordinal。

  • name():查看枚举常量值的名字。
  • ordinal():查看枚举常量值的序号。

通过enum定义的枚举类,其实也是一个class,只不过它有以下几个特点:

  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将enum类型用于switch语句。

因为枚举类也是class, 所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段。

十四,选择结构

Java的选择结构主要有 if 语句和 switch语句。switch语句是多分支结构。

1,if 选择语句

2,switch多分支结构

使用switch时不要忘记break,不要忘记default。

十五,循环结构

Java中的循环结构包括for循环,for each循环,while循环。

1,for循环

2,for each循环

for each循环可以对数组,字符串,各种容器类型进行遍历,其背后依赖于Iteratable接口。

3,while循环

4,流程控制continue、break

十六,异常处理

Java中的异常包括两种体系:Error和Exception.

Error指的是严重的错误,程序一般对此无能为力。如:

  • OutOfMemoryError:内存耗尽
  • NoClassDefFoundError:无法加载某个Class
  • StackOverflowError:栈溢出

而Exception则是运行时的错误,它可以被捕获并处理。

某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:

  • NumberFormatException:数值类型的格式错误
  • FileNotFoundException:未找到文件
  • SocketException:读取网络失败

还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:

  • NullPointerException:对某个null的对象调用方法或字段
  • IndexOutOfBoundsException:数组索引越界

Exception又分为两大类:

  • RuntimeException以及它的子类;
  • 非RuntimeException(包括IOException、ReflectiveOperationException等等)

Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
  • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。

异常捕获的语句是 try...catch...finally...此外还可以用throw抛出异常

如:throw new IllegalArgumentException。

十七,类的定义

Java中用关键字class定义普通类, 用enum定义枚举类,用abstract class定义抽象类,用interface定义接口。

我们先看普通类的定义和实例化。

类的定义中可以用public声明为公有属性和公有方法,在类的内部和外部都可以被访问。

可以用private声明为私有属性和私有方法,只允许在类的作用域访问,不允许在类的外部访问。

可以用protected声明为受保护的属性和方法,只允许在类作用域及其子类作用域中访问。

不使用作用域关键字声明的属性和方法默认为为package作用域,在同一个package中的类可以访问。

十八,构造方法

构造方法是类的一个特殊的方法,构造方法名就是类名。

构造方法没有return返回值,也没有void声明。

如果一个类没有定义任何构造方法,那么编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句。

如果我们已经定义了构造方法,那么编译器不会生成默认构造方法。

没有在构造方法中初始化属性时,引用类型的字段默认是null,int类型默认值是0,布尔类型默认值是false。

我们可以为一个类定义多个构造方法,使用时可以根据参数类型和数量自动进行匹配。

这叫做构造方法的重载。

所有方法都支持方法重载。

十九,静态属性和静态方法

通过static修饰的属性为静态属性,通过static修饰的方法为静态方法。

静态属性和静态方法属于类而不属于特定的实例,在类的实例之间共享。

可以通过类名直接调用静态属性和静态方法,也可以通过实例对象间接调用。

静态方法中不能够通过this关键字使用实例属性。

二十,继承

类和类之间有三种关系:A is B, A use B, A has B.

其中A is B 就是 继承关系。如果A 的属性中有 B的类型,叫做 A has B.如果A 的方法的参数中有 B的类型,叫做 A use B.

我们重点介绍继承关系。

Java中用extends声明继承关系。public, protected声明的属性和方法可以被子类继承,而private声明的属性和方法不可以被子类继承。

二十一,多态

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。

多态具有一个非常强大的功能,就是允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

这就实现了面向对象编程非常著名的开闭原则:对扩展开放,对修改封闭。

二十二,抽象类

使用abstract声明的方法为抽象类,抽象类只能够被继承,不能够创建抽象类的实例。

抽象类的方法可以被abstract声明为抽象方法,抽象方法没有执行语句。

抽象类的作用在于定义签名规范,具体的业务实现留给子类去做。

二十三,接口

在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。

如果一个抽象类没有字段,所有方法全部都是抽象方法,那么该抽象类就可以被改写成接口(interface)。

Java 中的 interface具有和 Scala中的 trait相似的功能。

一个class只能继承自一个父类,但可以继承自多个接口。

通过关键字 implements 声明class和interface之间的继承关系。

interface和interface之间也可以相互继承,使用关键字 extends来表示这种扩展关系。

interface不能有实例属性,但可以有静态属性。

interface中的所有方法都默认为抽象方法,因此无需关键字abstract声明。

interface的非抽象方法用default关键字声明,叫做default方法。

default方法中不能够引用实例属性,但可以调用抽象方法。

除了default方法和static声明的静态属性,interface基本上可以看成是一个躯壳。

二十四,反射

通常我们通过类来创建实例,但反射机制让我们能够通过实例来获取类的信息。

包括类的名字,类的属性和方法签名,类的继承关系等等。

当加载进一个class类文件时,JVM会创建一个Class类型的实例来保存类的信息。

1,获取Class类型实例

2,访问属性

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

3,调用方法

  • Method getMethod(name, Class...):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

4,调用构造方法

5,获取继承关系

二十五,泛型

泛型就是编写模板代码来适应任意类型。Java的容器类中大量使用了泛型。

泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查。

Java中泛型的实现是使用的擦拭法,编译器编译包含泛型的类时将泛型换成Object类型,

编译器编译泛型实例化的代码时根据泛型的具体类型进行安全转型,而JVM虚拟机对泛型一无所知。

因此泛型的类型不能是int,float,double等基本类型,并且不能够获取泛型的反射。

二十六,注解

Java中的注解是放在Java源码的类、方法、属性、参数前的一种特殊"注释",以@开头。

注解可以看成用作标注的一种"元数据"。

Java中有3中不同的注解:

  • SOURCE类型的注解由编译器使用,在编译期被丢掉了,如@Override;
  • CLASS类型的注解仅保存在class文件中,这类注解只被一些底层库使用,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

Java语言使用@interface语法来定义注解(Annotation),定义注解一般需要用到元注解。

元注解(meta annotation)就是可以用来修饰其它注解的注解。

Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

RUNTIME类型的注解如何使用,完全由程序自己决定。

二十七,Scala和Java对比

Java发明于1995年,Scala发明于2003年。

Scala和Java都是JVM语言,两者的源码都将编译成.class字节码在JVM虚拟机上执行。

因此Scala和Java可以无缝混编。

Scala在Java基础上做了重大的改进,使其兼备静态语言和脚本语言的特色。

下面列举一些比较显著的差异。

1,Scala比Java更加简洁

Java 中打印用 System.out.println, 而Scala用 println,类似Python。

Java 许多地方语句中的分号”;“不能省略, 而Scala可以省略,类似Python。

Java 声明变量时,先声明类型,再声明变量名,而Scala则先变量名,必要时用冒号说明类型,类似Python。

Java 定义方法无需关键字,Scala 定义方法用关键字 def,可读性更强,类似Python.

Scala支持for推导式,类似Python.

Scala 支持类型推断,Java 在后面的版本才增加了 var 关键字来支持类型推断。

Scala 支持隐式类型转换和隐式参数。

2,Scala比Java更加灵活

Java必须先编译后才能执行,Scala解释器可以直接运行Scala脚本。

Java编程风格统一为面向对象,Scala支持面向对象和函数式编程多种风格

Java中的多分支用switch, Scala使用match模式匹配实现多分支。

Java中的类支持静态属性和静态方法,Scala用伴生对象和伴生方法概念将静态属性和方法与实例属性和方法分离。

Java的循环中支持break和continue关键字,Scala的循环中不支持。

3,常用标点符号差异

Java中导入全部对象用星号作为通配符,Scala中用下划线作为通配符。

Java中用方括号来取索引,Scala中用圆括号来取索引。

Java中用尖括号来表示泛型,Scala中用方括号来表示泛型。

Java中的数组用花括号来表示,Scala中一般用工厂方法。

Java中可以用冒号来书写for each语句,Scala中用<- 来书写。

二十八,Java和C 的对比

C 发明于1983年,而Java发明于1995年。

C 代码直接编译成机器码运行在裸机上,而Java代码编译成字节码运行在虚拟机上。

C 编译的最终结果是一个程序生成一个exe文件。Java编译结果是一个程序中有多少类就生成多少个与类名相同的class文件。

Java的语法大量借鉴了C ,但和C 相比,Java是一门纯面向对象的语言,风格更加简洁统一。

下面列举一些两者语法上的差异。

1,C 导入package使用 #include, 而 Java使用 import 关键字 。

2,C 支持指针直接操控内存,而 Java 抛弃了令人困惑的指针功能。

3,C 使用析构函数回收垃圾,Java自动回收(GC算法)。

4,C 支持直接多继承性,Java用接口来实现多继承性。

5,C 中可以在类的外部可以定义函数,而Java不允许在类和接口外面直接定义方法。

0 人点赞