本来以前是学过Java的…但是…但是主要是使用前端的知识,久了没碰Java就快忘完了…
这也不是第一次把本就没学多少的Java知识忘得一干二净了,
这边正好赶上学校要开Java课程了,所以这次痛定思痛决定写个学习笔记,希望能帮助自己的同时也能帮助到大家。
本来想用IDEA的(毕竟方便又好看),但是为了适应学校的安排,还是用上了eclipse…
因为是前端的,这篇笔记主要是为了速通,所以很多知识点不会说太细
学什么前端,爷要全栈!
先要熟悉版本
三大版本:
JavaSE: 标准版
JavaME:嵌入式开发
JavaEE:企业级开发
我们这里使用JavaSE
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):直接点个外卖
package JavaLearner;
public class Animal {
String type;
String name;
public void eat() {
System.out.printf("吃东西");
}
}
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关键字:
由于存在多态这种操作,有时候我们难免会忘记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());
大概就是这样,当做语法糖看看就好
接口是为了使约束和实现相分离:“面向接口进行编程”
接口是建立在抽象上的,说白了就是一个抽象的集合(意思是里面不能有具体的方法)。
另外,接口是可以多继承的,弥补了类的不足
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 In1,In2,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的就先缓一缓吧…东西太多了学不过来…)