JAVA基础


前言

写在开篇

本来以前是学过Java的(指2021年1月左右)…但是..但是主要是使用前端的知识,久了没碰Java就快忘完了…
这也不是第一次把本就没学多少的Java知识忘得一干二净了,
这边正好赶上学校要开Java课程了,所以这次痛定思痛决定写个学习笔记,希望能帮助自己的同时也能帮助到大家。
本来想用IDEA的(毕竟方便又好看),但是为了适应学校的安排,还是用上了eclipse..

因为是前端的,这篇笔记主要是为了速通,所以很多知识点不会说太细
学什么前端,爷要全栈!

更新日志

2022/01/07

这篇博文是我在2021/08/23日在CSDN上发布的

点击这里跳转

现在做了一些小的调整后,将其搬至当前位置

前置准备

选择Java版本

先要熟悉版本
三大版本:
JavaSE: 标准版
JavaME:嵌入式开发
JavaEE:企业级开发

我们这里使用JavaSE

安装JDK

java development kit java开发工具包,安装完成之后配置环境变量即可
JDK包括了JRE(java运行环境),而JRE又包括了JVM(java虚拟机,java虚拟机的存在使得程序能够忽略系统的差异)
(具体操作自行查阅,不再赘述)

安装编译器

Eclipse,intelligent IDEA等都可以(记事本也行)

正式操作

新建内容

第一步:先新建一个项目
在这里插入图片描述

随便给个名字,这里叫EclipseNewbee(意思是Eclipse新手,不是牛逼..)
在这里插入图片描述
弹出一个窗口,内容不用修改。
不用细究,直接Create
在这里插入图片描述
第二步:新建一个包(可省略)
然后右键src新建一个Package,
Package相当于一个文件夹,不用想太多
(如果不建包的话也可以直接建一个Class然后写代码)
在这里插入图片描述
随便给一个名字,这里叫JavaLearner好了
在这里插入图片描述
第三步:新建一个类
在这里插入图片描述
这里要做的依旧是给个名字,其余的不用管
在这里插入图片描述
Finish后得到一个这样的

在这里插入图片描述
右边的两个对应左边的两个,不相同的话会报错,所以不要去修改

基础语法

这里默认为有 C语言 和 面向对象 语言的基础
(初学的话压力可能有点大)

主函数

package JavaLearner;

public class HelloWorld {
	public static void main(String[] args) {
			//别问,问就是 “这么写就对了”
	}
}

输入输出

后面默认给出代码和图片两种格式,分别方便 复制粘贴 和 阅读

package JavaLearner;

import java.util.Scanner;

public class HelloWorld {
	public static void main(String[] args) {
	    //两种写法都可以
		//Scanner scanner = new Scanner(System.in);
		//String s = scanner.next();
		String s = new Scanner(System.in).next();
		System.out.printf("Hello " + s);
	}
}

导入Scanner对象,然后实例化,next()是获取下一次输入的字符串并返回给 s
然后在输出的时候进行一个字符串拼接
在这里插入图片描述
运行效果:
在这里插入图片描述

分支

其实还是if和switch那些
基本上所有语言都差不多,大概看看就行

int a = new Scanner(System.in).nextInt();
//分支
if(a > 1000) {
	System.out.printf("这是%d\n",a);
}
else if(a > 800){
	System.out.println("没错,这是我了" + a + "\n");
}
else {
	System.out.print("hhh\n");
}

循环

依旧是差不多,所有语言基本一个样

for(int i = 0; i < arr1.length; i ++) {
	System.out.printf("%d ",arr1[i]);
}

增强for循环
也就是迭代器,遍历数组或许看不出来什么区别,但是遍历其他数据结构或者对象就很好用了
注意i取的是内容,不是下标

for(int i : arr1 ) {
	System.out.printf("增强for循环(迭代器):%d ", i)
}

数组

//数组
int[] arr1 = {1,3,5,7,9};
//浅拷贝
int[] arr2 = arr1;
arr2[0] = 6666;
//深拷贝
int[] arr3 = new int[arr1.length];
System.arraycopy(arr1, 0, arr3, 0, arr1.length);
arr3[2] = 2333;

深拷贝就是复制一个和原来一模一样的;
浅拷贝就是直接指向原来那个;

上述代码在遍历之后的结果是:
在这里插入图片描述
注意,我们只修改过arr2和arr3,但是修改arr2的时候arr1也发生了同步变化,这是因为arr1和arr2本来就是一个东西的两个名字罢了

但是修改arr3却不会对另外两个有影响,因为它是完全独立的复制品

增强for循环遍历二维数组

//int[][] arr4 = new int[3][];
//上述写法也可以,注意至少要明确行数
int[][] arr4 = {
				{1,2,3},
				{4,5,6},
				{7,8,9}
				};

for(int[] i : arr4 ) {
	for(int j : i) {
		System.out.printf("%d ", j);
	}
	System.out.printf("\n");
}

方法

其实可以近似地理解为函数
和函数的区别大概是:
函数是相对独立的,方法是附在对象上的
所以c只有函数,java只有方法

public static int fib(int n) {
	if(n > 0)
		return n + fib(n - 1);
	else
		return 1;
}

这个写在class里面,main外面(如果有main的话)

面向对象进行开发

什么是面向对象?
首先就得说一下什么是面向过程。
C语言就是面向过程的,它的每一步操作都要自己做,包括内存的分配释放等等;
Java等就是面向对象的,它的许多操作是已经被封装好了,我们使用的时候直接调用就好了;

比喻一下就是:
面向过程(POP):自己买菜一步一步做菜
面向对象(OOP):直接点个外卖

初识类与对象

class本意就是类
这里新建一个类,内容如图
在这里插入图片描述

package JavaLearner;

public class Animal {
	String type;
	String name;
	public void eat() {
		System.out.printf("吃东西");
	}
}

接下来到之前的文件内
在这里插入图片描述
在main中运行以下代码

Animal lion = new Animal();
Animal tigger = new Animal();
lion.eat();

在这里插入图片描述
其中,Animal就是一个类,lion和tigger是对象
世界上的任何东西都可以作为一个类,
对象就是具体化的类(准确地说叫实例化

比如我们常说的人类就是一个类,
我们可以将其不同程度地实体化为年轻人、工程师、张三李四

构造器

在刚才,你会发现,type和name这两个值没有赋值,这时候他们的值默认null
如果我们再去主函数里面赋值,那感觉好low逼啊
所以这里出现了构造器


也叫构造方法(C++里面叫构造函数)
满足以下两个特点:
1.名称与所在类的名称相同
2.不写任何返回类型,包括void
3.在new实例化对象的时候触发

package JavaLearner;

public class Animal {
//构造器
	public Animal(String type, String name) {
		this.name = name;
		this.type = type;
		System.out.println(name + "被初始化了!");
	}
	
	String type;
	String name;

	public void eat() {
		System.out.printf("吃东西");
	}
}

这里涉及到了this指针,this指针就是字面意思,指代当前所处的类或对象;
this.name指的是类或对象的属性;
name这里指的是参数;


在这里插入图片描述
main里面运行结果
在这里插入图片描述
另外,如果是一个没有写入构造方法的类,那么默认自动生成一个空的构造方法

重载

问题来了,加了构造器之后可是要传递参数的,那假如用户不穿怎么办?

package JavaLearner;

public class Animal {
	//第一种情况,两个参数
	public Animal(String type, String name) {
		this.name = name;
		this.type = type;
		System.out.println(name + "被初始化了!");
	}
	//第二种情况,一个参数
	public Animal(String type) {
		this.type = type;
		this.name = "莫得名字";
	}
	//第三种情况,没有参数
	public Animal() {
		this.name = "莫得名字";
		this.type = "某种动物";
	}
	
	String type;
	String name;

	public void eat() {
		System.out.printf("吃东西");
	}
}

这样一来就能根据用户传入的参数来自动选择执行哪个构造方法
(注意,重载方法是根据参数的个数和类型来区分的,不是参数名称!)

重载不仅可以用于构造方法,只要是方法,那么都可以重载。

接下来说面向对象三大特征


面向对象三大特征: 封装

就是把对象包裹起来,并且控制对象内容的访问权限
大概就是为了所谓的高内聚低耦合
高内聚:类的内部数据操作细节不由外部干涉
低耦合:仅暴露少量的方法给外部使用

修饰符

在这里插入图片描述

内容访问权限的控制是通过修饰符来完成的
类的属性通常用private修饰,这样可以避免误操作修改,避免了不可预测得混乱

private String type;
private String name;

public String getName() {
	return this.name;
}
public void setName(String name) {
	this.name = name;
}

通过getName()和setName()操作,保证了 访问的时候不会被误操作修改修改的内容一定是字符串
并且你还可以通过if判断等方式进一步地规避误操作

这样一来安全性高多了

面向对象三大特征: 继承

继承就是将已有的类进行修改和拓展形成的新的类
(其实就是字面意思

先新建一个Pet类,使用extends关键字继承Animal类
在这里插入图片描述

package JavaLearner;

public class Pet extends Animal{
	public Pet(String toy) {
		this.toy = toy;
		System.out.printf("他喜欢玩" + this.toy);
	}
	String toy;
}

main里实例化一下,
调用自身构造函数之外,还会调用父类的无参构造
在这里插入图片描述
如果父级的属性是public修饰的,那么子级就会直接继承并且可以直接访问;如果不能直接访问,那么可以用封装里面提到过的方法来给子级传递数据;

这里我们用protected修饰,给父类Animal添加一个属性:

protected String size;

然后在子类Pet里面用super关键字向上访问父类:
(父级,也可以叫超级,超类,就是因为其关键字是super,
super会向上查找,直到查找到符合条件的父类)

package JavaLearner;

public class Pet extends Animal{
	public Pet(String toy, String size) {
		this.toy = toy;
		//访问到父级的size属性
		//由于继承的存在,这个属性在实例化后将变成自己的
		super.size = size;
		System.out.println("他喜欢玩" + this.toy);
	}
	String toy;
}

回到main函数里面:

Pet cat = new Pet("锤子","big");		
Pet dog = new Pet("球","huge");

System.out.println("这是继承的父类的属性: " + dog.size);
System.out.println("这是继承的父类的属性: " + cat.size);

在这里插入图片描述
可以发现,这里我们先实例化cat对象,后实例化dog对象,
但是先输出dog对象,后输出cat对象
我们发现,虽然是一个父类的两个子类,但是继承一般数据类型(非引用类型)属性是通过深拷贝实现的,他们的属性没有发生覆盖,不会相互干扰

另外,super和this,可以通过形如super()或this()的操作调用构造函数,但是注意两点:
1.super只能出现在子类的方法中
2.super()或this()需要放在构造函数的第一行(由于这个性质,他们两个不能同时存在)

继承的其他注意事项
注意java只有单继承,意思是一个子类只能有一个直接父级,
可以多重(单)继承(子类–父类–爷爷类(间接父级)–)
不是多继承(多个直接父级)
不是多继承!!!
不是多继承!!!

所有类的最终父级是Object类,这种终极父类被称为“基类”(基岩基石那个意思,别多想啊)

重写

重写全称是方法重写,为了更好地和重载区分,我们先分析一下两者地字面意思
重载:多重挂载
重写:重新写一遍
比如在父类Animal中有一个方法:

public void action() {
	System.out.printf("捕猎");
}

子类中也有一个同名的方法

public void action() {
	System.out.printf("吃饭喝水睡觉");
}

这就构成了重写,
不过这还不够规范,为了提醒代码的阅读者,我们还会在重写的方法上加上@Override

@Override
public void action() {
	System.out.printf("吃饭喝水睡觉");
}

其实也就是一个从自身出发向上寻找指定内容的过程,优先使用最先找到的指定内容(也就是JavaScript中的作用域链)
在这里插入图片描述
当然,如果不希望一个方法被重写,可以给它加上final关键字进行修饰,有了final修饰的类,不能被继承

抽象

什么是抽象?就是没有具体内容
比如一个抽象类:

public abstract class xxx{
//.....(当然也可以什么都不写)
//要写的话可以写抽象方法等
}

不能直接实例化一个抽象的对象,因为其没有具体内容!
它存在的目的就是为了适应不同情况,举个例子,
生物这个类里面有个方法是输出生物的日常活动,
但是生物很多,要执行这个方法的话,固定的代码可能不能很好地适应每一种情况
所以就需要被重写——既然会被重写,那一开始我们就没必要给它具体内容,只需要在这里放一个名称提醒我们还有这个方法就好了。
注意事项:

1.抽象类不能创建对象(不能new),并且有抽象方法的类一定要是抽象类。
2.抽象类只能通过儿子继承然后覆盖重写所有的抽象类方法进行使用。抽象类可以有构造方法,是供子类创建对象时,初始化父类成员使用的。
3.抽象类中不一定有抽象方法,但有抽象方法一定是抽象类。
4.抽象类的子类除非也是抽象类,否则就要重写所有父类方法。
(如果子类也是抽象类,那么可以选择不重写或重写一部分)
5….中的抽象方法,没有方法体!!!

父类的引用指向子类
新建两个类,分别是A,B

package JavaLearner;

public class B {
	public void test() {
		System.out.println("这是B");
	}
}
package JavaLearner;

public class A extends B{
	public void test() {
		System.out.println("这是A");
	}
}

主函数内:

在这里插入图片描述
至于为什么执行A的方法,是因为,优先在自身寻找,自身没有才找父类。
这里自身有test()方法,所以调用A的test方法

其实上面的内容就是多态

面向对象三大特征: 多态

一个对象的多种形态,比如Kitty是cat类也是animal类。
或者说,一个人的父亲,必定也是一个人的儿子。
这就是多态的字面意思。

概括为父类引用指向子类对象。
比如原来是Zi zi = new Zi(),
多态的写法就是 Fu zi = new Zi();
(即“左父右子”。同时,这种操作叫做向上转型,这种操作一定是安全的)
说明:
1.直接通过对象名称访问成员变量,优先访问等号左边,没有就向上找
编译看左边,运行也看左边
2.间接通过成员方法访问(调用方法的话),优先访问等号右边(new的对象),没有就向上找。
编译看左边,运行看右边

第二种说明:
(如果前一种不是那么好理解的话)
记住编译看等号左边就行,别管右边new的是什么,左边怎么声明的就是声明。

解释:
编译看左,运行看右。
假设Fu中有方法A,B子类有方法A,C,
现在我们Fu zi = new Zi();那么:

Fu zi = new Zi();
zi.A();//父子都有,优先用子
zi.B();//子没有,向上找到父

zi.C();
//编译报错,编译看左(Fu),Fu是没有C的

为什么使用多态
比如

CAT cat = new CAT();
DOG dog = new DOG();
DUCK duck = new DUCK();

…….
这样一来东西一多,就很难知道他们的继承关系了

所以我们可以:
ANIMAL cat = new CAT();
…….
继承关系一目了然!

转型

还是用比喻来说吧..
向上转型:
比如猫转为动物,即左父右子的多态(FU zi = new Zi),一定是安全的
向下转型:
比如动物转为猫,类似强制类型转换,如(Fu zi = (Zi)fu)
这样是有风险的,可能导致未知错误发生。所以动物向下转为猫的前提是原先是从猫向上转来的,这个例子可以推广到一般情况。

instanceof

instanceof关键字:
由于存在多态这种操作,有时候我们难免会忘记ANIMAL到底是DOG,CAT还是PIG什么的…所以我们就有了instanceof!

//判断 一个对象 是不是 一个类的 子类 的实例
//A是B的子类,a是A的实例
		B a = new A();
		B b = new B();
		System.out.println(a instanceof B);

由于返回值是布尔类型,还可以利用这个关键字做很多事情!(不在话下)

静态

概念

从public static void main开始就在用了
static修饰的变量和普通变量区别在于,进厂时机不同
好吧,是分配内存空间的时机和位置不同
有static修饰的内容,在写下之后就分配进了方法区,而且还是多线程的
没有static的内容,要等到类加载之后才会存在(这个学到代理和反射再说)

所以对于static修饰的内容,可以直接这么访问而不需要实例化:
类中:

public static int age = 233;//静态变量

主函数中:

在这里插入图片描述
上面的内容再口语一点就是,static约束的内容会“直接出生”,早于其他非static的内容

静态方法

在类里写一个:

static {
	System.out.println("这是Animal的一个静态代码块");
}

主函数里:

Animal pig = new Animal();
Animal frog = new Animal();
Animal seal = new Animal();

运行结果是:
在这里插入图片描述
可见,执行了一次静态代码块的函数,三次构造函数
这说明,静态的优先执行,并且一个类的静态内容只会执行一次

那么这和静态方法有什么关系呢?
静态方法写法上的区别就是多个名字之类的嘛…..

static int hhh(){
	System.out.println("这是Animal的一个静态代码块");
	return 123;
}

当然,静态方法需要自己调用才会执行。

静态导入包

导入包在输入输出就提过了
不过静态地导入包是什么鬼?

//常规导包
import java.lang.Math;
//静态导包(只能导入包内的一个方法)
import static java.lang.Math.random;
//一般的操作		
System.out.println(Math.random());
//静态导包后直接用
System.out.println(random());

大概就是这样,当做语法糖看看就好

接口

创建接口

接口是为了使约束和实现相分离:“面向接口进行编程”
接口是建立在抽象上的,说白了就是一个抽象的集合(意思是里面不能有具体的方法)。
另外,接口是可以多继承的,弥补了类的不足

接口创建是建立一个interface
在这里插入图片描述

package JavaLearner;

public interface UserMethods {
	public abstract void add(String name);
	//不写约束的话,默认为public abstract
	void delete(String name);
	void update(String name, String newName);
	void query(String name);
}

接口是抽象的集合,那么我们就需要一个类来实现这些抽象

实现接口

新建一个类来重写接口中的抽象方法,这个类就叫做接口的实现类,其命名规范是 人类看得懂的名字 + Impl

这里要使用关键字implements进行实现

package JavaLearner;

public class UserMethodsImpl implements UserMethods{
	@Override
	public void add(String name){
		
	}
	@Override
	public void delete(String name) {
		
	}
	@Override
	public void update(String name, String newName) {
		
	}
	@Override
	public void query(String name){
		
	}
}

由于接口可以多继承(准确地说是“多实现”,但是这样说很奇怪不是吗)
所以允许存在以下写法:

public class Impl implements In1In2,In3{
	//当然还是需要重写,不然会报错
	//三个接口的内容直接在这里重写就行
	//不过这里我就不写了,大家懂这个意思就行
}

异常

啥是异常就不多解释了吧,就是用户或者程序员或者系统造成的各种未知的bug

Java把异常当成对象进行处理,
并且定义了一个基类java.lang.Throwable作为所有异常的终极父类,其下有两个直接子类,分别是Error和Exception

Error是JVM抛出的,大多数时候和代码语法关系不大(比如内存溢出,这只能怪内存太小了?),但是导致的错误往往是致命的,JVM往往会因此终止线程;

Exception则一般是有程序本身的逻辑引起的,可以被程序处理;

抛出和捕获异常

try是指要检测的部分
catch参数要写错误的类型对象(这个得去查一下有哪些,实在不行Throwable吧)
如果触发了这个类型的错误,那么就会执行其中的代码
finally是检测完毕时执行(无论如何都会执行,可以不写这一部分)

int a = 1, b = 0;
try {
	System.out.println(a/b);
}catch(ArithmeticException e) {
	System.out.println(e);
	System.out.println("太强了这就是抛出和捕获异常吗");
}finally {
	System.out.println("检测完毕");
}

比如,0作为除数
在这里插入图片描述
注意事项:
如果要捕获多种异常的话,可以用使用多个if else那种格式来书写多个catch达到目的
catch中的参数是一个对象,其中内置了很多处理错误的方法可以了解一下

后记

JavaSE基础部分大概到这里就结束了,如果内容上还有不足或疏漏之处,欢迎指正。
后面我会继续做JavaWeb相关的学习笔记(Vue-Cli的就先缓一缓吧….东西太多了学不过来…)


文章作者: Serio
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Serio !
  目录