Java简介
Java由SUN
公司开发,现在被Oracle
公司收购,是目前排名第一的编程语言。
- Java分为:
Java SE, Java ME, Java EE
三个版本,分别对应不同的开发场景,其中Java SE
是标准。 - Java的特性:
- 简洁高效。相比较于
C++
,Java去除了头文件,指针,结构体,多继承,运算符重载等容易混淆的问题,确定了基本数据类型的长度。 - 可移植性。Java基于
JVM
运行,使得Java的可移植性大大加强。 - 垃圾回收机制。Java采用了
GC(Garbage Collection)
机制,不需要关注内存的释放。 - 函数式编程。Java8引进了
Lambda
表达式以及Stream
类,使得高效的函数式编程得以实现。 - 支持网络编程、多线程编程、安全性高等。
- 简洁高效。相比较于
1. Java的可移植性
Java对源文件(*.java
)进行编译后产生字节码文件(*.class
),字节码文件由JVM
解释执行。由于JVM
是跨平台的,不同的操作系统都有对应的JVM
,故Java是跨平台的。
2. Java开发环境
JDK
:Java Development Kit
,Java开发工具包,提供了两个很重要的命令,javac, java
。JRE
:Java Runtime Environment
,Java运行环境,安装JDK
后,一般会自动安装有JRE
。- 环境变量:安装好
JDK
后,还必须配置环境变量。JAVA_HOME, CLASSPATH, Path
。
3. Java初步
- 类:Java以类为单位,类只能用
default
和public
修饰。一个Java文件中有且只有一个public
类,可以有若干个default
类,JVM
只会访问public
类,所以只有此种类才能执行。- 有
public
类的文件,文件名必须与public
类名相同。 - 没有
public
类的文件,文件名可随意。 - 在一个
.java
文件中的多个类,在编译后会自动将不同的类保存在不同的.class
文件中。
- 有
- 主方法:
public static void main(String[] args)
,存在于public
类中,是JVM
的入口。
4. CLASSPATH环境属性
当使用java
命令去解释一个.class
文件时,就会启动一个JVM
进程,而这个进程需要一个类加载路径,这个路径就是CLASSPATH
指定的。
- 如果将
CLASSPATH=D:\mldjava
,此时在任意目录下启动java
命令,JVM
的类搜索路径都是D:\mldjava
。这样并不好。 - 所以
CLASSPATH=.
,这个点表示当前目录。
Java基础语法
注释
- 单行注释:
//
。 - 多行注释:
/* xxx */
。 - 文档注释:
/** xxx */
。
package app;
/**
* 该类的主要作用是在屏幕输出信息
* @author zghong
* @date 2019.11.9
* @param x 参数x
* @return 计算结果
*/
public class App {
/* 多行注释
* 哈哈
*/
public static void main(String[] args) throws Exception {
// 输出Hello Java
System.out.println("Hello Java");
}
}
标识符
标识符由数字、字母、下划线、dollar符号构成,不能以数字开头。不能用Java的关键字。
- 由于Java中的字符采用
Unicode
编码,所以Java支持中文标识符,但是不推荐。 - 关键字:
public, private, protected, static, abstract, final, byte, short, int, long, float, double, char, boolean, instanceof, if, else, switch, case, while, do ,for, break, continue, return, try, catch, finally, assert, class, interface, extends, implements, import, package, new, null, super, this, throw, throws, void
。instanceof
:判断对象所属的类型,返回boolean
。如a instanceof int, haha instanceof Person
等。
- 约定俗称的命名规则:
- 类名,文件名:大驼峰命名法。
- 变量名,方法名:小驼峰命名法。
- 常量:所有字母大写,单词之间用
_
隔开。
数据类型
Java的数据类型分为基本数据类型和引用数据类型。基本数据类型有byte, shrot, int, long, float, double, char, boolean
8种,引用数据类型有class, interface, 数组
3种。
基本数据类型
- Java在
java.lang
中定义了7种基本数据类型对应的类实现:Byte, Short, Integer, Long, Float, Double, Char
,里面包含着丰富的类属性和静态方法,如SIZE, MAX_VALUE, MIN_VALUE, xxValue(), toString(), parseXx(str)
等。 - 整数默认的是
int
,小数默认的是double
。- 所以写超过
int
范围的整数时必须末尾加L(l)
。 - 定义
float
类型的数据必须加f
!!(C/C++不需要强制加f
,但是Java强制要加)
- 所以写超过
- 自动类型转换和强制类型转化:不同数据类型之间是可以相互转换的。小类型到大类型是自动,大类型到小类型必须强制!!!
- 自动类型转换:
byte, short, char, int, long, float, double
,这是自动转换的顺序,从小到大。如果涉及计算,计算的结果是最大的类型。 - 强制类型转换:
type(x)
。
- 自动类型转换:
- Java的
char
采用的Unicode
编码,故支持中文字符。 - Java不支持使用使用数字代替
boolean
类型。
String类
String类是一个十分重要的类,基本可以看成一个基本数据类型。
String类的实例化对象
String类并不是基本数据类型,但是它可以直接赋值定义,也可以用构造方法定义,但是二者有本质的区别。
- 直接赋值定义:
String a = "hahah"
。 - 构造方法定义:
String a = new String("hahah")
。
字符串的比较,字符串常量池以及两种实例化方法比较
- 基本数据类型可以直接用
==
比较内容,但是引用类型而言,==
比较的是堆地址,equals
方法才是比较内容。==
:字符串所引用的堆地址是否一致。equals
方法,继承于Object
类,比较内容是否一致。
- 字符串常量,形如
"haha", "hehe"
等直接由"
包围的字符串称之为字符串常量,从严格意义上讲,字符串常量是String
类的匿名对象。- 字符串常量可以直接调用实例化方法,说明字符串常量就是
String
类的一个实例化对象,只不过这个匿名对象是有系统自动生成的。 String a = "haha"
,首先系统自动创建匿名对象haha
,然后引用赋值给a
。- 字符串常量池:为了防止生成的匿名对象过多而造成内存浪费,
JDK
对结构进行了优化,提供了一个字符串常量池,所有通过直接赋值实例化的String
类对象都自动保存在字符串常量池中,可以重复使用。- 静态常量池:
JVM
在加载.class
文件时,对于确定的字符串常量可以提前分配好内存。如a = "haha", b = "haha" + "hehe"
等。 - 动态常量池:
JVM
在加载时不能确定的字符串常量,只能在运行时确定的。如a = "haha" + str1
,由于不能确定str1
的内容,无法提前实例化匿名对象。 - 字符串常量池的意义就是为了重复使用!
- 静态常量池:
- 字符串常量可以直接调用实例化方法,说明字符串常量就是
- 用
new
方法实例化的对象:String a = new String("haha")
,这里分了两步骤:- 首先系统自动创建字符串的匿名对象
"haha"
。 - 然后使用
new
方法开辟一个新的堆内存空间,用匿名对象"haha"
去初始化该新创建的对象。 - 最后匿名对象失去引用,成为垃圾,被自动回收。
- 所以使用
new
会消耗两个内存空间,只不过匿名对象会被自动回收而已。
- 首先系统自动创建字符串的匿名对象
字符串修改对内存的消耗
由于字符串是基于数组实现的,故字符串是不可变数据类型,而所有对字符串对象内容的修改都是通过引用的变化实现的。
String a = "a";
System.out.println(a.hashCode()); // 97
a += "b";
System.out.println(a.hashCode()); // 3105
a += "c";
System.out.println(a.hashCode()); // 96354
// 下面这种代码会产生很多内存垃圾,导致GC性能下降,从而使得整个程序性能下降
// String b = "";
// for(int i=0;i<100;i++) b += i;
String类的常用方法
String(char[] a[, start, end]), String(byte[] a[, start, end]), charAt(idx), toCharArray(), getBytes(), equals(str), equalsIgnoreCase(str), compareTo(str), compareToIgnoreCase(str), contains(str), indexOf(str[, start]), lastIndexOf(str[, start]), startsWith(str[, start]), endsWith(str[, start]), replaceFirst(old, new), replaceAll(old, new), split(str[, max]), substring(start[, end]), concat(str), format("%d, %s", a, b), isEmpty(), length(), trim(), toUpperCase(), toLowerCase(), intern()字符串实例化对象入常量池
JavaDoc
是Java官方文档,可以下载或者在线查看。- 字符数组和字符串可以相互转化:
String(char[] a[, start, end]), toCharArray()
。 - 字节数组和字符串可以相互转化,在网络编程中尤为常见!!!:
- Java中字符串不能直接
str[0]
获取某个位置的字符,必须通过函数charAt(idx)
实现。 format()
是一个静态方法。
数组
数组是一种引用类型,所以数组涉及堆栈空间的分配以及引用传递的问题。栈(存储变量名),堆(存储具体的数据)。
数组的定义
- 数组是引用类型,用
new
开辟堆内存空间,一块堆内存空间可以被多个栈内存空间所指向。 - Java中数组是动态初始化为类型的默认值,也可以在定义时静态初始化为指定值。
- 获取数组的长度:
name.length
。
// 数组是引用类型,默认值是null
int[] a = null;
a = new int[10];
// 可以在定义时分配空间,动态初始化为0
int[] b = new int[10];
// 静态初始化
int[] c = new int[] {1, 2, 3};
// 二维数据
int[][] d = new int[10][10];
// 二维数组是两个一维数组
int[][] e = new int[2][];
e[0] = new int[10];
e[1] = new int[20];
数组的输出
Java中数组的输出除了利用C风格的循环遍历的方式,在Java8之后还增加了foreach
输出的特性。
int[][] a = new int[][] {{1, 2, 3, 4}, {5, 6, 7, 8, 9}};
// 循环输出
for(int i=0;i<a.length;i++) {
for(int j=0;j<a[i].length;j++) {
System.out.print(a[i][j] + "、");
}
System.out.println();
}
// foreach输出
for(int[] temp : a) {
for(int x : temp) {
System.out.print(x + "、");
}
System.out.println();
}
数组类库的支持
由于数组使用的频率很高,官方也提供了许多与数组有关的操作,封装在java.lang.Arrays
类中。
- 填充:
public static void fill(object[] a[, int begin, int end], object val)
- 排序:
public static void sort(object[] a[, int begin, int end])
- 二分查找:
public static int binarySearch(object[] a, object key)
- 比较:
public static boolean equals(object[] a, object[] b)
运算符
- 算术运算符:
+, -, *, /, %, ++, --
- 比较运算符:
>, >=, <, <=, ==, !=
- 逻辑运算符:
&&, ||, !
,为短路运算符。 - 位运算符:
&, |, ~, ^, >>, <<
- 赋值运算符:
=, +=, -=, /=, %=, &=, |=, ~=, ^=
- 条件运算符:
condition ? a:b
- 其他:
(), []
三种逻辑结构
顺序结构
条件结构
- 类型1
if(condition1) {xxx}
else if(condition2) {yyy}
else {zzz}
- 类型2
switch(整型 | char | enum | String){
case a1:xx;break;
case a2:yy;break;
default:zz;
}
循环结构
- 类型1
while(conditon) {xxx}
- 类型2
do {
xxx
}while(condition);
- 类型3
for(int a;a<1oo;a++) {xxx}
- 类型4
for(int a : list) {xxx}
类型4仅适用于有forEach特性的变量,如数组,集合类等。
方法
方法也称函数,是一个最小的代码块集合。
方法的定义
[访问控制修饰符] [非访问控制修饰符] 返回值类型 方法名(参数) {
方法体;
[return 返回值;]
}
不定长参数
Java的方法可以接受不定长参数,不定长参数必须放在参数列表的最后,形如type... name
。在方法的内部,不定长参数会打包成一个数组。
方法的重载
方法重载是方法名称重用的一种方式,要求方法名相同,参数的类型或者参数的个数不同。
方法的递归调用
方法的递归调用是特殊的嵌套调用,是自己调用自己的一种方式。相比较循环,递归可以让代码清晰,但是递归所消耗的资源也是最大的,因为递归涉及函数的入栈和出栈,保存现场需要大量的资源。同时,递归方法在方法体有着特殊的规定:
- 有递归边界。
- 在每次递归时需要改变传参的值。
OOP
面向对象之前是面向过程,不适合大型项目的开发。面向对象的特点是抽象,封装,继承,多态。
- 抽象,将现实进行抽象成逻辑结构。
- 封装,对现实实体的属性和行为方法进行封装,包括隐藏性封装。
- 继承,OOP的重要概念,是提高开发效率的体现。在Java中只支持单继承,但是支持对接口的多实现。
- 多态,OOP的有一个重要概念,多态是指同一操作在不同实体上产生不同的结果。Java中支持两种形式的多态:
- 方法重载:一个类中,允许多个同名方法,但是参数类型或者参数个数不同。
- 对象多态:子类的向上转型。
类的定义与初始化
[访问控制修饰符] [非访问控制修饰符] class 类名 {
[访问控制修饰符] [非访问控制修饰符] 数据类型 成员属性;
[访问控制修饰符] [非访问控制修饰符] 返回值类型 方法名(参数列表) {}
}
类名 变量名 = new 类型(初始化参数);
堆内存与栈内存
- 堆内存
heap
:保存的是对象的具体信息(成员属性),在程序之中堆内存的开辟是通过new
完成的。 - 栈内存
stack
:保存的是一块堆内存地址,即通过地址找到堆内存,而后找到对象内容,简单的理解为对象名称保存在栈内存中。 - 关于方法的保存:属性是每个实例化对象所特有的,但是一个类的方法却是所有实例化对象所共有的,故一个类的方法会保存在全局方法区这样的公共内存空间中。
引用传递和内存垃圾
- 类是一种引用数据类型,引用的核心是堆内存和栈内存的分配和指向管理。一块堆内存可以被多个栈内存引用,每一块栈内存都保存有一个堆内存的地址信息。
- Java的
GC(Garbage Collection)
是指当一个堆内存没有栈内存引用时,该堆内存就成为了内存垃圾,就会被GC
机制自动回收。
构造方法和析构方法
构造方法和析构方法是两种特殊的方法,在对象被实例化和销毁时被执行。
构造方法
- 构造方法的名称与类名相同。
- 构造方法没有返回值,无需定义返回值类型。
- 如果没有自己写构造方法,系统会自动定义一个默认的无参的构造方法。一旦自己写了构造方法,该默认无参的构造方法将不会生成。
- 构造方法支持重载。
public Person() {
name = "无名氏";
age = -1;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
匿名对象
new
的作用是开辟堆内存空间,对象的名称是为了对该堆内存的应用,这样可以防止堆内存成为垃圾内存。如果没有对象名的引用,这种一次性对象叫匿名对象。
- 匿名对象是一次性的,除非有栈内存空间对其进行引用。
- 有时候匿名对象是很有用的,可以减少定义无用的变量名,如定义对象数组,方法传参等。
this与super
this
是当前实例对象的引用。- 调用本类的属性值。
- 调用本类的实例化方法。
- 调用本类的构造方法,必须放在其他构造方法的首行。
- 合理使用
this
可以做到代码简洁,减少多余的赋值操作。
class A {
private long num;
private String name;
private double salary;
public A() {
this(-1, "无名氏", -1);
}
public A(long num) {
this(num, "无名氏", -1);
}
public A(long num, String name) {
this(num, name, -1);
}
public A(long num, String name, double salary) {
this.num = num;
this.name = name;
this.salary = salary;
}
}
super
是当前实例对象父类的引用。
析构方法
析构方法在Java中并不是一个核心概念,但是提供了这么一个方法finalize()
,当对象要被删除时调用之。
protected void finalize( )
{
super().finalize();
// finalization code here
}
static关键字
当类中的某个属性要定义为整个类所共有的属性或方法时,则可以在声明属性或方法前加非访问限制控制符static
,这类属性和方法可以在没有实例化对象时调用。
- 使用
static
修饰的属性不在栈内存中保存,而是放在全局数据区中。 - 使用
static
修饰的属性和方法可以不用实例化对象就可以被调用。 - 静态方法只能使用静态属性,不能使用实例化属性。
- 静态方法中不能使用
this, super
等关键字。
项目开发中类的相关事项
项目开发中类的约定俗称的事项
- 类中的所有属性都必须使用
private
封装,封装后必须提供setter(), getter()
。 - 类中可以提供无数个构造方法,当必须保留无参构造方法。
- 类中不允许出现任何输出语句,所有内容的获取都必须返回。
数据表与简单Java类的映射转换
在实际开发项目中,从数据库中查询的数据需要利用Java处理,这时就需要利用Java的类进行保存相关数据,要学会利用类来对数据表结构的描述。
- 数据表和类之间的基本映射关系如下:
数据表 | 类 |
---|---|
数据实体表的设计 | 类的定义 |
表中的字段 | 类的成员属性 |
表的外键关联 | 对象引用关联 |
表的一行记录 | 类的一个实例对象 |
表的多行记录 | 对象数组 |
继承
Java中只支持单继承,消除了C++中多继承存在的各种毛病。
继承的实现
class 子类 extends 父类 {}
。子类也叫作派生类,父类也叫作超类。
子类对象的实例化流程
在继承中,子类需要重用父类的结构,所以在子类实例化对象时,首先会默认调用父类的无参构造方法,为父类进行实例化,而后再进行子类的构造调用,进行实例化。
- 默认会自动调用父类的无参构造方法。
- 可以在子类构造方法的首行显式地调用父类的无参构造方法或其他有参构造方法。
- 当显式地构造父类的有参构造方法时,后面的子类构造方法就不需要对已经初始化的属性再进行初始化。
重写(Override)
子类在继承了父类的方法后,可以对父类的方法进行重写。重写与重载不同,重写要保证方法名,参数个数和类型,返回类型等都不能改变,同时访问权限不能比父类低。
- 使用
this.方法名(), 方法名()
调用的是子类中的方法。 - 使用
super.方法名()
调用的是父类中的方法,包括被重写的方法。 - 重写(Override)与重载(Overload)的区别:
区别 | 重载 | 重写 |
---|---|---|
参数 | 必改 | 不能改 |
返回值类型 | 可改 | 不能改 |
访问权限 | 可改 | 不能降低访问权限 |
Java继承的限制
- Java只支持单继承,不支持多继承。
- 父类的构造方法不能被继承。
- 子类只能显式地继承非私有属性/方法,但是私有属性/方法也继承了,但是不能直接调用,属于隐式继承。
final关键字
final
关键字一般用来定义不可继承的类,不能重写的方法,全局常量等。
- 使用
final
修饰的类不能被继承。 - 使用
final
修饰的方法不能被重写。 - 使用
final
定义常量,如public final int ON = 1
等。 - 使用
final
定义全局常量,如public static final String INFO = "hahah"
等。
多态
多态的特点是,同一结构在执行时会根据不同的形式展示出不同的结果。
方法的多态
- 方法的重载。
对象的多态
对象的多态的前提是,存在继承,重写,转型三个必要条件。对象的多态有:自动向上转型(占比90%),强制向下转型(占比10%)。
对象向上转型
父类 父类实例 = 子类实例
,自动完成。- 子类可以当做父类使用,具体执行时,执行的还是子类方法。
对象向下转型
子类 子类实例 = (子类)父类实例
,强制类型转换。- 如上,当某个子类的实例化对象被父类变量a所引用,a只能调用父类的方法或者子类重写了的父类方法。
- 如果a需要调用子类所特有的方法,则需要
(子类)a
来进行强制类型转换成子类对象才可以调用子类所特有的方法。 - 故:必须先发生向上转型,才可以使用向下转型。
接口也是可以多态的
- 接口同类一样,某一个接口A被某个类B所实现,类B中要实现接口A中的所有方法,此时可以实例化一个接口对象a,它指向类B的一个实例化对象b。
- 接口的实例化对象a,可以正常调用接口中所定义的,类中所实现的所有方法。
Object类
Object是所有类的基类,所有的类都默认继承于该类。
- 利用向上转型的特点,
Object
类型可以接受任何对象。 Object
类拥有的方法:toString()
,该方法必须返回一个字符串,用于输出对象时显示输出的内容。hashCode()
,该方法返回实例化对象的地址的哈希值。equals(Object obj)
,该方法用于比较对象的内容。