JAVA
第一章 JAVA基础
1.jdk jre jvm
JDK: Java标准开发包。提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java类库等。
JRE: Java运行环境,用于解释执行Java的字节码文件。
JVM: Java虚拟机,是JRE的一部分。负责解释执行字节码文件,是可运行Java字节码文件的虚拟计算机
区别联系: JDK包含JRE,JDK和JRE中都包含JVM。JDK除了包含JRE,还包含一些常用开发工具和基础类库。JDK用于开发Java程序,JRE用于运行Java程序。JVM是Java编程语言的核心,并且具有平台独立性。
2.开发Java程序需要的三个步骤
- 编写源程序
- 编译源文件生成字节码文件
- 加载运行字节码文件
3.Java程序运行过程
javac a.java //编译.java源文件生成.class字节码文件
java a.class //运行java文件
4.Java程序语句执行顺序
顺序结构、选择结构、循环结构、异常处理逻辑结构
第二章 编程基础
1.Java的基本语法
方法格式:
权限修饰符 返回值类型 方法名(参数列表){
方法内容;
return 返回值;
}
public int sum(int a,int b){
int sum=a+b;
return sum;
}
权限修饰符:
类内部 | 本包 | 子类 | 外部包 | |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
一个包里面只能有一个公共类(public)。default又叫友好型
注释:// /* */ /** **/
关键字: 被占用的名称。
2.变量的数据类型
变量的定义:
按所属的数据类型划分:基本数据类型变量、引用数据类型变量
按被声明的位置划分:局部变量、成员变量
内存图:
变量的类型转换:
boolean
类型不能转换成任何其他数据类型。
自动类型转换:容量小的类型自动转换成容量大的数据类型 byte,short,int -> float -> long -> double
byte,short,int
不会相互转换。他们三者在计算时会转换成int
类型
强制类型转换:容量大的类型转换成容量小的类型时,要用强制类型转换符 a = (int)b;
变量的作用域:
成员变量:在方法外,类内定义的变量。在整个类里起作用。
局部变量:在方法里定义的变量。只在方法里起作用。
常量:
final int a;
注意事项:
float a= 4.2f; //4.2默认为double类型,如果不加f,会报错
long a = 222222222222l; //带上l
char c = 's'; //带上''
3.运算符
算术:+ - * / % ++ --
赋值:=
关系运算符:> < >= <= == !=
逻辑:! && ||
位运算符:>> << $ | ~ ^
条件运算符: 三目运算符XX?X:X
4.选择和循环
if(a>0){
}else if(a<0){
}else{
}
switch(a){
case 1: //当a==1时
//执行内容
break;
case 2: //当a==2时
break;
default: //当a==其他的时候
break;
}
for(int i=0; i<10; i++){
}
while(i<10){
}
do{
}while(i<10)
/*
* for( 总的的每一个元素的数据类型 别名:要遍历的、总的 ){
* 操作这个别名
* }
*/
int a[] = new int[]{1,2,3,4,5,6,7};
for(int a1 : a){
a1 = a1+10;
}
5.数组
多维数组初始化: 没有赋值时,值默认为0
//动态声明01
int[][] arr = new int[3][];
arr[0] = new int[3]; //第0行有三列
arr[1] = new int[2]; //第1行有两列
arr[2] = new int[1]; //第2行有一列
//动态声明02
int [][] arr2 = new int[3][2];
arr2[0][0]=33; //第0行0列为33
//静态声明01
int arr3[][] = new int[][]{{1,2,3},{2,3}};
数组的常见操作:
//声明数组
int arr[];
//数组初始化
int a[] = new int[]{2,1,3};
int a1[] = new int[3];
//查看数组长度
a.length;
//for each遍历
for(int m:a){
m=m+1;
}
//数组的拷贝
int b[]=a;
int b1[] = Arrays.copyOf(a,2);
// int arr2[] = Arrays.copyOf(arr,自定义长度);
//数组排序
Arrays.sort(a); //从小到大排序
//将int数组转换为字符串
Arrays.toString(a);
6.输入输出
输入:
Scanner s = new Scanner(System.in);
String next = s.next();
int i = s.nextInt();
float v = s.nextFloat();
输出:
System.out.println("输出"); //ln表示换行
System.out.print("输出"); //不会换行
System.out.printf("输出i为:%d", i); //不会换行
7.类与对象
三大特征:
- 封装: 通过访问控制关键字实现属性或方法的封装,仅对外提供公共访问方式。
封装的好处:实现对数据项和方法的隐藏;实现隐藏隔离,允许外部对类做有限的访问,开发者可以自由的改变类的内部实现;提高了代码的重用性 - 继承: 通过
extends
实现继承。
两个好处:代码重用;通过继承实现对现实世界更准确的建模 - 多态: 一个对象变量可以指向多种实际类型对象的现象被称为“多态”。
三个必要条件:继承、方法的重写、父类引用指向子类对象
多态的好处:提高了代码的维护性(继承保证);提高了代码的扩展性
Java中多态的实现方式:接口实现,继承父类进行方法重写,同一个类中进行方法重载。父类引用指向子类对象
实例化对象(对象的创建):
Student stu = new Student();
类的定义:
[修饰符] class 类名 [extends 父类名] [implements 接口名]{ //中括号里的,可有可无
// 类体,包括类的成员变量和方法
/* 具体方法如下:
* 1.get.set方法
* 2.构造函数:包括一个全参的构造函数和一个无参的构造函数
* 3.自定义的函数函数
*/
}
8.继承
object类: object类是所有类的父类,里面有很多方法
类的继承格式:
public class Child extends Parent{
}
子类有父类非私有(private)的方法和成员变量
重写父类方法:
public class Child extends Parent{
public void myParent(){
System.out.println("我是子类的重写");
}
}
9.类的封装
为什么要进行封装: 将类的某些信息隐藏在类的内部,不允许外部程序直接访问。而是通过该类提供getter/setter
的方法来对隐藏的信息进行操作和访问
封装的实现步骤:
- 修改属性的可见性,将其设为
private
- 创建
getter/setter
方法(用于属性的读写) - 在
getter/setter
方法中加入属性控制语句(对属性的合法性进行判断)
public class Person {
private int age;
private String name;
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setAge(int age){
if(age <0){
System.out.println("age不合法,请重新输入");
}else {
this.age = age;
}
}
public void setName(String name){
this.name = name;
}
}
class Test2{
public static void main(String[] age){
Person person = new Person();
person.setAge(60);
person.setName("hello");
System.out.println(person.getAge());
System.out.println(person.getName());
}
}
10.构造方法
定义: 在创建对象时初始化对象,即为对象成员变量赋初始值,总与new一起在创建对象的语句中使用,一个类可以有多个构造函数。可根据其参数个数的不同或参数类型的不同来区分他们。
public class Person {
private int age;
private String name;
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(int age){
this.age = age;
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void setAge(int age){
if(age <0){
System.out.println("age不合法,请重新输入");
}else {
this.age = age;
}
}
public void setName(String name){
this.name = name;
}
}
class Test2{
public static void main(String[] age){
Person person = new Person("hello", 60);
System.out.println(person.getAge());
System.out.println(person.getName());
}
}
11.方法的重载与重写
重载的概念:
- 必须在同一个类中
- 方法名相同
- 方法的参数的个数、顺序或类型不同
- 与方法的修饰符或返回值没有关系
public class Person {
public void myPrint(){
System.out.println("我是重载方法1");
}
public int myPrint(String name){
System.out.println("我是重载方法2");
return 1;
}
public int myPrint(int age){
System.out.println("我是重载方法3");
return 1;
}
}
重写的概念: 当父类中的方法无法满足子类需求的时候,需要方法重写
方法重写的注意事项:
- 重写的方法必须要和父类一模一样
- 重写的方法可以使用
@Override
注解来标识 - 子类中重写的方法的访问权限不能低于父类中方法的访问权限:
private < 默认 < protected < public
public class Parent {
protected void myPrint(){
System.out.println("Parent");
}
}
class Son extends Parent{
@Override
public void myPrint(){
System.out.println("son");
}
}
class Test{
public static void main(String[] args) {
Son son = new Son();
son.myPrint();
}
}
重写重载的区别
重载:在同一个类中,出现多个同名的方法,参数列表不同,与返回值类型,修饰符无关
重写:子类中出现和父类中一模一样的方法(包括返回值类型,方法名,参数列表),子类中的访问修饰符大于等于父类的访问修饰符。
12.this/super关键字
this关键字: Java中,为解决变量的命名冲突和不确定性问题而引入this
关键字,this
代表对当前对象的引用
用法:
注意:this
只能在非静态类中使用,静态方法和静态的代码块中绝对不能出现this。原因是static
方法在类加载时就已经存在了,但是对象是在创建时才在内存中生成的。
super关键字: super关键字主要存在于子类方法中,用于指向父类对象。可以访问父类的属性、函数以及构造函数。
用法:
- 子类父类中存在同名成员时(包括变量和方法),在子类中默认是访问子类的成员,可通过super关键字指定访问父类的成员。
- 默认会先调用父类无参的构造方法,可通过super关键字指定调用父类的构造方法。
13.static/final关键字
static关键字: 被static修饰的变量属于类变量/方法,可以通过类名、变量名/方法名直接引用,而不需要new一个累出来。
final关键字: final修饰的类不能被继承。final修饰的方法不能被重写,但是可以被使用。修饰的变量就不能再改变了。
14.抽象类
15.接口
接口和抽象类的区别:
16.多态
继承是多态的前提。(继承包括extends
和implements
)
父类引用指向子类对象:Father father = new Son();
public class Parent {
private String name = "hello";
protected void myPrint(){
System.out.println("Parent");
}
protected void parentPrint(){
System.out.println("Parent");
}
}
class Son extends Parent{
@Override
protected void myPrint(){
System.out.println("Son");
}
}
class Test{
public static void main(String[] args) {
//多态写法:左侧父类的引用指向右侧的子类
//子类父类都有,输出子类的;子类没有,往上找
Parent obj = new Son();
obj.myPrint();
obj.parentPrint();
}
}
17.异常
Throwable类是所有错误与异常的父类,只有该类型的对象才能被throw
抛出,它有两个子类:
-
Error
:灾难性错误 -
Exception
:表示异常,是可以解决的问题。有一个子类叫RuntimeException
,是java程序运行过程中出现的问题,是运行期异常。
RuntimeException: 以下是一些常见的RuntimeException
及其子类异常:
-
ArrayIndexOutOfBoundsException
(数组索引越界异常):当尝试访问数组中不存在的索引时抛出。 -
IllegalArgumentException
(非法参数异常):当传递给方法的参数不合法或无效时抛出。 -
IllegalStateException
(非法状态异常):当对象的状态不适合执行特定操作时抛出。 -
ArithmeticException
(算术异常):当发生算术错误,例如除以零时抛出。 -
NumberFormatException
(数字格式异常):当字符串无法转换为数字类型时抛出。
异常处理的作用: 对方法传递过来的参数进行合法性效验。如果参数不合法,就用抛出异常的方式告知方法的调用者,传递的参数有问题。
throw关键字的作用: throw关键字可以在指定的方法中抛出指定的异常
使用格式:throw new xxxException("异常产生的原因");
if(arr == null){
throw new NullPointerException("传递的数组的值是null");
}
注意:
- throw关键字必须写在方法的内部
- throw关键字后边new的对象必须是Exception或Exception的子类对象。
- throw关键字抛出异常后,我们就必须处理这个异常对象:
创建的是RuntimeException
或RuntimeException
子类的对象,我们可以不处理,默认交给JVM处理
创建的是编译异常,我们就必须处理这个异常,要么throws要么try-catch处理
使用throws抛出异常: 异常处理的第一种方法----交给调用者处理,最终交给JVM处理(中断处理)
使用格式:
/*
修饰符 返回值 方法名(参数列表) throws AAAException,BBBException{
throws new AAAExcpeiton("产生原因");
throws new BBBExcpeiton("产生原因");
}
*/
public static void readFile(String fileName) throws FileNotFoundException{
if(! fileName.equals("c:\\\\a.txt")){
throw new FileNotFoundException("传递路径错误"); //编译异常
}
}
注意:
- throws关键字必须写在方法声明处
- throws关键字后面声明的异常必须是Exception或Exception的子类
- 方法内部如果抛出了多个异常,那么throws后边也必须声明多个异常
如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可 - 调用了一个声明抛出异常的方法,我们就必须处理声明的异常:
要么继续使用throws声明抛出异常,最终交给JVM处理;要么try-catch自己处理异常
使用try-catch-finally处理异常: 异常处理的第二种方式----自己处理异常
使用格式:
/*
try{
可能产生异常的代码
}catch(定义一个异常变量,用来接收try重抛出的异常对象){
异常的处理逻辑,接收异常对象后怎么处理异常对象
一般会把异常的信息记录到一个日志中
}
...
catch(异常类名 变量名){
}finally{
}
*/
public static void main(String[] args){
try{
readFile("d:\\a.tx");
}catch(FileNotFoundException e){
System.out.println("文件路径错误");
}
System.out.println("后续代码");
}
public static void readFile(String fileName) throws FileNotFoundException{
if(! fileName.equals("c:\\\\a.txt")){
throw new FileNotFoundException("传递路径错误"); //编译异常
}
}
注意:
- try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
- 如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑后执行finally块,然后继续执行后续代码
如果try中没有产生异常,那么就不会执行catch中的异常处理逻辑,执行完try中的代码后执行finally块,继续执行后续代码 - finally块必须执行,与异常是否抛出无关
- 多个catch块会按照从上往下的顺序进行匹配,当匹配到某个catch块时,就会进入到这个catch块中而忽略掉后续的所有catch块(只进入一个catch)
自定义异常类: Java提供的异常类不够使用时,需要自己定义一些异常类
格式:
/*
public class XXXException extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个异常信息的构造方法
}
*/
public class RegisterException extends Exception{
//空参数的构造方法
public RegisterException(){
super();
}
//带异常信息的构造方法
public RegisterException(String message){
super(message);
}
}
注意:
- 自定义异常类一般都是以Exception结尾,说明该类是一个异常类
- 自定义异常类,必须继承Exception或者RuntimeException
继承Exception:自定义异常类就是一个编译期异常,必须处理,要么throws要么try-catch
继承RuntimeException:自定义异常类就是一个运行期异常,可以不处理交给JVM处理(中断处理)
18.多线程
继承Thread创建多线程:java.lang.Thread
是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
Java程序属于抢占式调度,哪个线程的优先级高,哪个线程就先执行;同一优先级,随机选择一个执行
实现步骤:
- 创建一个Thread类的子类
- 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
- 创建Thread类的子类对象
- 调用Thread类中的方法start方法,开启新线程,执行run方法
void start():使该线程开始执行;JVM调用该线程的run方法。
结果是两个线程并发地运行;当前线程(main线程)和另一个线程(执行其run方法)。
多次启动一个线程是非法的,特别是当前线程已经结束执行后不能再重新启动。
//1.创建一个Thread类的子类
public class MyThread extends Thread{
@Override
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务
public void run(){
for(int i = 0; i<20; i++){
System.out.println("run:",i);
}
}
}
class test{
public static void main(String[] args){
//3.创建Thread类的子类对象
MyThread mt = new MyThread();
//4.调用Thread类中的方法start方法,开启新线程,执行run方法
mt.start();
for(int i = 0; i<20; i++){
System.out.println("main:",i);
}
}
}
实现Runnable接口实现多线程的实现步骤:
- 创建一个Runnable接口的实现类
- 在实现类中重写Runnable接口的run方法,设置线程任务
- 创建一个Runnable接口的视线对象
- 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中的start方法,开启新线程调用run方法
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run(){
for(int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class test{
public static void main(String[] args){
//3.创建一个Runnable接口的视线对象
RunnableImpl run = new RunnableImpl();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(run);
//5.调用Thread类中的start方法,开启新线程调用run方法
t.start();
for(int i = 0; i<20; i++){
System.out.println("main:",i);
}
}
}
同步代码块:synchronized
格式:
/*
synchronized(锁对象){
可能会出现线程安全问题的代码
}
*/
public class RunnableImpl implements Runnable{
//定义一个多线程共享的票源
public int ticket = 100;
//创建锁对象
Object obj = new Object();
//设置线程任务:买票
@Override
public void run(){
while(true){
//同步代码块
synchronized(obj){
if(ticket>0){
//提高安全问题出现概率
try{
Thread.sleep(10);
} catch(InterruptedException e){
e.printStackTrece();
}
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程中使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码中执行
线程之间的通信:wait/notify
。调用wait方法会放弃cpu的执行,进入到WAITING状态(无限等待);调用notify方法来唤醒,会继续执行wait之后的代码。必须保证wait和notify同时只有一个在执行。同步使用的锁对象必须保证唯一,且只有锁对象才能调用wait和notify。