我的java学习笔记
这是我当初学习java时的一些笔记,这几天翻出来看了一下,感觉可以作为一些参考,由于当时我有C/C++的基础,所以学习的速度非常快,有些地方直接和C/C++进行了对比,所以在观看的时候最好有一些编程基础。
语言只是算法的载体,会得再多,也仅仅是获得了一个又一个躯壳,真正称得上灵魂的是算法,一个没有灵魂的躯壳,只能算一个行尸走肉。
开始前的一些准备
Java的三大版本
- JavaSE:标准版(桌面应用程序,控制台开发)
JavaME:嵌入式开发(手机app)- JavaEE:企业开发
JDK、JRE、JVM
- JDK:Java开发者工具(用来开发Java程序)
- JRE:Java运行环境(可以运行Java的程序)
- JVM:Java虚拟机(核心部分)
虚拟机机制
.java
文件编译成.class
文件,在虚拟机(JVM)上运行,实现“一次编译处处运行”。
垃圾回收机制
C/C++是程序员手动回收内存,高效准确,但容易失误。
java自动垃圾回收,可以自动检测内存,虽然规避了失误,但不会非常高效。
环境搭建
官网下载JDK,尽量使用稳定版本,推荐使用压缩包进行解压,之后再进行环境变量配置,在版本上可以更好的更换。
配置环境变量包括:JDK\bin
和JDK\jre\bin
。
检测方法,输入java -version
会出现:
1 | java version "1.8.0_101" |
第一个程序
建立
Test.java
文件,用来写代码。写下代码:
1
2
3
4
5
6
7
8public class Test{
public static void main(String[] args){
// 打印后不会换行
System.out.print("hello world");
// 打印后自动换行
System.out.println("hello world");
}
}注意,类的名字和文件名字要一致。
打开命令窗口,cd到目标文件的路径,输入以下指令进行编译:
1
javac Test.java
编译完之后就会出现一个名字相同的
.class
文件。运行
Test.class
文件,需要输入以下程序:1
java Test
基本语法
这一部分的特点是java的基础部分,大部分非常和C/C++类似,所以有点基础的话简单看看就基本了解。
注释
单行注释:同C/C++,
//注释内容
多行注释:同C/C++,
/*注释内容*/
文档注释:java特有
1
2
3
4
5
6
7/**
*类名
*描述
*@author 作者名
*@version 版本
*@since jdk版本
*/命名规范
不是强制,但更具有规范性。
- 包名:所有字母小写。
- 类名、接口名:大驼峰命名。
- 变量名、方法名:小驼峰命名。
- 常量名:所有字母大写,多单词时用下划线分割。
数据类型
注:八种基本数据类型之外全是引用数据类型。
基本数据类型
- 整型
byte
:1字节short
:2字节int
:4字节long
:8字节
- 浮点型
float
:单精度(7位有效数字),4字节double
:双精度(16位有效数字),8字节
- 字符型:
char
- 布尔型:
boolean
引用数据类型
- 类
- 接口
- 数组
String
类型
引用类型的一种,当出现两个相同的内容时,会分配相同的内存。
可以使用“+”来进行拼接,例如:
1 | String str = "hello " + "world"; |
注:只要有一个String类型进行相加时,就会合并成字符串,但前面会进行直接计算。
数据类型转换
基本转换
(char、byte、short) —> int —> long —> float —> double
强制转换
举个例子:
1 | int number = 30; |
虽然强制转换很不错,这种操作会出现溢出现象,而且只针对基本数据类型。
注:布尔类型不能转换成其他类型。
运算符
数学运算符、赋值运算符、逻辑运算符、比较运算符、位运算符、三元运算符,这些操作和C/C++类似,不再赘述。
执行顺序
条件语句、循环语句、特殊语句(break、continue、return),这些操作和C/C++类似,不再赘述。
数组
声明和初始化
变量声明完就要初始化,这涉及到一些java运行机制,所以必须这样操作。但在声明数组的时候可以不用初始化,值得注意的是,java数组声明和C/C++有点不同:
1 | // 声明一个int数组 |
和C/C++不同点主要是可以把中括号直接放前面。
虽然可以不用初始化,但一般还是直接初始化,这里需要使用new关键字,new作用就是分配空间。
以下为动态初始化:
1 | // 声明一个int数组,并初始化 |
动态初始化不会进行赋值,所以初始化完后会是默认值,初始化同时需要说明数据类型和分配大小。
还有一种静态初始化:
1 | // 声明一个int数组,并初始化 |
这种直接初始化并赋值,不用说明空间申请大小。
内部方法和属性
每个数组提供了一个length属性,可以利用这个属性直接查看这个数组的大小:
1 | int len = num.length; |
多维数组
其实就是每个数组中的每个元素就是一个数组,这样可以进行不断的数组嵌套。
书写方式:
1 | // 动态初始化 |
核心部分
java是一个强面向对象的语言,比C++和Python还要强调对象的概念,这部分主要去讲面向对象的一些操作。
类
写一个类
示例:
1 | public class Persion{ |
成员变量是可以不用初始化的,因为有默认值。
新建文件,类和文件名相同。
实例化一个类
示例:
1 | // 声明一个Persion对象,并实例化 |
匿名对象
不定义对象句柄,直接实例化,接着使用内部的方法。
1 | new Persion().showName(); |
特点就是只使用一次就可以,或者将其作为实参直接传递。
类的属性
语法格式:修饰符 类型 属性名 = 初值
修饰符
public
:公有,外部可以直接访问。private
:私有,只能通过类的内部访问。
成员变量的两种方式
实例变量:最普通的变量,类实例化之后才能使用
类变量:在中间加上
static
进行修饰,类不用实例化,直接通过类名.属性
就可以使用,存在于静态域。static
的方法只能访问static
的变量,但普通方法可以访问static
变量。
类的方法
完成某些功能的函数。
参数传递
java的参数传递方式很有意思,准确来讲是值传递。
首先讲一下虚拟机的内存模型:
虚拟机中有栈(stack)、堆(heap)和方法区(method):
- 栈(stack):保存了所有的基本数据类型和引用数据类型的地址。
- 堆(heap):所有对象,引用数据类型的地址指向这里。
- 方法区(method):所有class和static变量
当使用基本数据类型的时候,由于值传递,直接是拷贝了数据放在栈中,函数结束的时候直接销毁。
当使用引用数据类型的时候,由于值传递,拷贝了这个引用类型的地址,所以放在栈中的是该对象的地址(也就是指针),那么接下来使用的时候该地址指向了原对象,这就很有意思了,直接变成了引用传递。
方法重载
一个类中可以存在多个同名方法,前提是参数的个数不同或者类型不同。
多参数传递
使用数组传递可变参数
1 | // 定义 |
使用java特有方式
其实和数组的使用方式相同。
1 | // 定义 |
总结:
- 第二种可以不写参数,但使用第一种的时候需要将null(空数组)传递进去。
- 必须放在最后,前面部分放其他类型。
包
java没有命名空间的概念,但包的管理解决了命名冲突的问题,同时方便管理了其他的程序。
本质就是一个文件夹,文件夹中的每一个程序文件里最开始都有一句语句:
1 | package 顶层包名.子包名; |
这句指明该类所属的包,如果同一个包下调用文件可以不用说明。
比较常用的命名方式:
- 小写,这样可以区分类文件
- 使用公司倒置命名,例如百度的域名:
www.baidu.com
,可以命名成com.baidu.www
调用的方式也很简单(上面的例子):import com.baidu.www.Demo
封装
修饰符的区别
修饰符 | 类内部调用 | 同一包内调用 | 子类调用 | 其他地方 |
---|---|---|---|---|
private |
Yes | |||
(缺省) | Yes | Yes | ||
protect |
Yes | Yes | Yes | |
public |
Yes | Yes | Yes | Yes |
构造方法
构造函数与类名相同,没有返回值,当实例化这个对象的时候就会调用这个方法。
1 | public class Persion{ |
如果定义了构造函数,就不会使用默认的构造函数。
同样道理,构造函数也存在重载,可以提供多种初始化。
1 | public class Persion{ |
this关键字
表示当前对象,可以调用自己成员,主要是区分形参和类的成员变量的重名。
1 | public class Persion{ |
可以让this()
为自己调用自己的构造函数,但必须有一个不用this()
的构造函数。
继承
示例:
1 | public class Student extends Persion{ |
使用extends
继承了Persion
类,拥有Persion
类的所有成员。
注:不要仅为了一个功能去继承。
java只支持单继承,不允许多继承,允许多层继承。
方法重写
方法重写可以更改从父类继承过来的函数,方便更好的使用。
要求:
- 必须拥有相同的函数名、形参和返回值。
- 不能使用更严格的访问权限。
- 需要同时为
static
或非static
。 - 子类抛出异常不能大于父类重写异常。
理论上直接去重写就可以,加上@Override
会更直观一点。
我使用的是idea,快捷键是Ctrl + o
可以快速重写。
super关键字
作用:
- 可以访问父类的属性。
- 可以调用父类的方法。
- 可以调用父类的构造方法。
多态
具体体现在重写(针对子类重写)和重载(重名)上。
关键字static
只有通过new
关键字才能实例化对象,关键字static
修饰的变量或方法是类所有,实例化的对象可以共享此变量或方法。不想频繁修改或调用,就可以用static
修饰的变量或方法,这样可以节省内存。
其他操作
instanceof
操作符
检查一个对象是否是一个类型,如果是就返回true
。
1 | // 检查e是否是Persion类型 |
Object
类
是所有类的根父类(基类),即使没有继承,默认也继承基类。
如果想用一个对象作为形参,但不知道需要用什么数据类型时,可以使用Object
类型,这样可以接受各种参数:
1 | public void test(Object obj){ |
类型转换
子类可以直接转换父类:
1 | Student s = new Student(); |
父类需要强制转换成子类:
1 | Persion p = new Persion(); |
注:无继承关系不能转换。
类比较大小
==
运算符:比较的只是值,基本类型就是直接比较大小,引用类型比较的是引用指针,指向同一个对象的时候会返回true
。equals()
方法:继承了Object
的类都具有这个方法,和==
相同,也是指向对象的是否相同。1
obj1.equals(obj2);
对File、String、Data以及包装类来说比较的是内容是否相等。
字面量创建对象的时候,只会在常量池中引用其地址,而用new
来分配内存创建对象的时候,会创建对象再考虑常量池。简单来讲字面量创建对象会节省内存。
默认状态下,equals()
方法比较的是地址,当然也可以自己定义比较的规则:
1 | public class MyDate{ |
包装类
这是对基本数据类型的封装,这样可以使用一些方法:
基本数据类型 | 包装类 |
---|---|
boolean |
Boolean |
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
char |
Character |
float |
Float |
double |
Double |
1 | int num = 10; |
注:1.5版本后支持自动装箱和拆箱。
常用操作:类型转换
1 | // 字符串转数字 |
.toString()
的重写
默认情况下,会返回内存地址,重写可以修改返回内容。并可以直接用变量去返回。
1 | Persion p = new Persion(); |
初始化代码块
下面的类包含了一个初始化代码块:
1 | public class Persion{ |
作用就是对java对象进行初始化,执行顺序就是:声明成员变量的默认值,多个初始化块共同执行,执行构造函数。
初始化代码块可以拥有多个,执行顺序从上到下。
注:静态代码块在类被声明就开始执行,只执行一次
final
关键字
这个关键字可以让程序更加安全。
final
标记的类不能被继承final
标记的方法不能被重写final
标记的变量只能赋值一次(常量,一般写成大写)
抽象类
针对于父类,不要去写具体方法,在接下来继承中主要有针对地去写。
用abstract
修饰,可以修饰类和方法,含有抽象方法的类必须声明成抽象类。
抽象类不能实例化,只能作为父类被继承。
不能去修饰属性、私有方法、构造函数、静态方法、final
标记的方法。
接口
java不支持多继承,所以可以使用接口来实现多继承的效果。
接口是抽象方法和常量值的集合,本质上是一种特殊的抽象类,只包含常量定义和方法定义,没有实现过程。
1 | // 定义接口 |
特点:
- 用
interface
来定义 - 默认成员变量全是
public static final
修饰 - 默认方法全是
public abstract
修饰 - 没有构造函数
- 采用多继承机制
注:生成文件最好是接口文件。
类可以继承多个接口:
1 | public class SubClass implements InterfaceA,InterfaceB,InterfaceC{ |
接口可以继承接口:
1 | public interface InterfaceA extends InterfaceB{ |
注:必须实现接口中所有的方法(重写),否则就是一个抽象类。
接口好处是,修改接口内容,抽象类会不稳定,如果我们新建一个接口,让类去多继承,可以避免这些。
1 | public class SubClass extends ClassA implements InterfaceA,InterfaceB,InterfaceC{ |
内部类
顾名思义,类内定义一个类。
其他部分
这部分就比较零散,主要涉及到一些其他操作。
main
函数
1 | public static void main(String[] args){ |
public
:可以外部调用static
:类方法void
:无返回值String[] args
:可以传递字符串数组
在使用命令行运行的时候,可以在后面添加其他参数:
1 | java Test abc 123 jjj999 |
后面部分会进入到args
数组中,并且可以为接下来提供参数。
异常处理
程序运行难免会出现错误,一般出现异常就会直接停止程序。
异常分类:
Error
:JVM内部错误,例如资源消耗严重。Exception
:偶尔存在的外部错误,例如读取不存在的文件。
异常捕获
示例:
1 | try{ |
不知道捕获异常类型时,直接Exception e
进行处理。
一些操作:
1 | // 直接打印异常 |
抛出异常
示例:
1 | public class ClassA{ |
一般是一个函数去主动抛出异常,如果遇到继承关系,子类重写时也需要抛出异常,但不能写范围小异常。
集合
存放于java.util
包中,是存放变量的容器。
- 集合只能存放对象,基本数据类型会转化为对应的封装类。
- 存放的方式为引用类型。
- 可以存放不同类型。
- JDK5之后增加了泛型,可以记住对象中的数据类型。
分类:
Set
:无序,不可重复集合。List
:有序,可重复集合。Map
:具有映射关系集合。
HashSet
用Hash算法来存储集合中的元素,因此不能保证存储顺序,而且不能重复,不是线程安全。
使用方法:
1 | public class Test{ |
TreeSet
确保集合排序状态,默认情况下采用自然排序。
使用方法:
1 | public class Test{ |
ArrayList
有序的可重复的集合,每个元素都有对应的索引,默认顺序为添加顺序。
使用方法:
1 | public class Test{ |
HashMap
就是键值对,键不允许重复。
使用方法:
1 | public class Test{ |
工具类:Collections
主要提供一些有用的函数,这里不再赘述。
泛型
java泛型只在编译过程有效,编译后直接擦除对应的信息,不会到运行阶段。
泛型类
具体使用和定义方法和C++类似。
定义方法:
1 | // T代表的是类型 |
泛型接口
定义方法:
1 | // T代表的是类型 |
泛型方法
注:只能在函数内部使用。
静态方法不能使用类定义泛型,如果想使用,只能使用静态方法自己定义的泛型。
定义方法:
1 | class A{ |
泛型通配符
使用一个List
集合充当参数的时候,可以使用?
充当通配符。
1 | public class Test{ |
枚举类
如果一个对象有限且固定,可以使用枚举来限制,例如:四季。
定义方法:
1 | enum Season{ |
规定了构造函数只能使用自己定义的几个。
使用方法:
1 | Season season = new Season.SPRING; |
这样就可以单独返回规定的对象,而且获得的是相同对象(地址一样)。
注解
程序员可以在程序中嵌入信息,不会改变逻辑,会保存在name=value对中。
@Override
:限定重写父方法,只能用于方法。@Deprecated
:表示某个类或方法已过时。@SupperWarning
:抑制编译器的警告。
IO流
- 文件流:基于文件的操作。
- 缓冲流:数据基于内存的操作。
其他内容流懒得写了,看这网站吧。
扩展部分
线程
- 程序:为了完成某个任务。
- 进程:正在运行的过程。
- 线程:进程细分成线程,一个程序的一条执行路径。
Thread实现多线程
通过java.lang.Thread
类来实现,每个线程通过Thread对象的run()
来实现,通常把run()
方法称作线程体。通过start()
方法调用这个线程。
构造方法:
Thread()
:创建新的Thread对象。Thread(String threadname)
:创建新的Thread对象,并指定名。Thread(Runnable target)
:实现Runnable接口来创建线程目标对象。Thread(Runnable target, String threadname)
:实现Runnable接口来创建线程目标对象,并指定名。
方法一:
继承Thread类,重写run()
,创建子对象的时候就表示创造了线程对象,调用start()
方法的时候就可以启动线程。
1 | // 继承Thread |
1 | public class Test{ |
方法二:
定义子类,实现Runnable接口,重写run()
,使用Thread的构造方法来实现,此方法局限性更小。
1 | // 继承Runnable |
1 | public class Test{ |
优先级
优先级为1~10,数字越大,优先级越高。默认设置优先级为5,
常用方法
start()
:启动线程run()
:线程内容getName()
:返回线程名称setName()
:设置线程名称static currentThread()
:返回当前线程getPriority()
:获取优先级setPriority()
:设置优先级static yield()
:线程让步join()
:线程强行加入(阻塞),一般用try
sleep()
:延时,单位毫秒stop()
:强制线程结束
线程的生命周期
过程:
- 新建:实例化一个Thread对象。
- 就绪:运行
.start()
之后,等待CPU时间片。 - 运行:线程就绪之后,进行运行状态。
- 阻塞:特殊情况时终止自己执行,进入阻塞。
- 死亡:完成全部工作或强制终止。
线程通信
wait()
:当前线程挂起并放弃CPU、同步资源,让别的线程可以进行访问并修改。当前线程状态为排队等候,等待被唤醒。notify()
:唤醒排队等候的线程。notifyAll()
:唤醒排队等候的所有线程。