java初级

学习自b站up主青空の霞光的教程

 将idea新ui代替旧ui的步骤:

按两下Shift键出现搜索框,输入“registry”找到ide.experimental.ui,将其勾选上,重启IDEA就变成新的UI样式了。

注释:

单行注释:// 多行注释:/**/  /**来进行更加详细的文档注释:

final关键字定义的变量为常量,无法重复定义

为什么会出现以上的情况呢,注意跟整型常量赋值给浮点型变量一样,会隐式类型转换不同,由于float类型精度不如double,这个直接赋值是对float类型变量赋值了一个double类型数据,会不合法。

总结一下隐式类型转换规则:byte→short(char)→int→long→float→double

字符类型 char 

字符类型也是一个重要的基本数据类型,它可以表示计算机中的任意一个字符(包括中文,英文, 标点符号等一切可以显示出来字符)表示的范围是0-65535

注意String类型:

这种类型并不是基本数据类型,它是对象类型

布尔类型

布尔类型是Java中的一个比较特殊的类型,它并不是存放数字的,而是状态,它有下面的两个状态:true 真 false 假。布尔类型(boolean)只有truefalse两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句(不同于C语言,C语言中一般使用0表示false,除0以外的所有数都表示true)布尔类型占据的空间大小并未明确定义,而是根据不同的JVM会有不同的实现。

运算符

不同类型之间也可以进行运算:

注意:不同类型的整数一起运算,小类型需要转换为大类型,short、byte、char一律转换为int再进行计算(无论算式中有无int,都需要转换),结果也是int;如果算式中出现了long类型,那么全部都需要转换到long类型再进行计算,结果也是long,反正就是依大的来。

 字符串拼接:

字符串不仅可以跟字符串拼接,也可以跟基本数据类型拼接:

 

 然后可以用转义字符char a = '\n' 或者 '\"'表示特殊的字符

乘除方面需要注意的是

如果是两个int类型的值进行除法运算,正常情况下为1.6,但由于结果也是整数,所以最后小数部分被丢弃了。 

 如果是两个小数一起计算的话,因为结果也是小数,所以说就可以准确得到结果:

同样的,整数和小数一起计算,由于所有的整数范围都比小数小,根据转换规则,整数和小数一起计算时,所有的整数都会变成小数参与运算,所以说最后的结果也就是小数了,同样可以得到正确的结果 

优先级运算符结合性(出现同优先级运算符时)
1-(负号) +(正号)从右向左 左结合性        
2* / %从左往右 右结合性
3+(加法,包括字符串) -(减法)从左往右 右结合性
4=从右向左 左结合性

 括号运算符

加减法优先级比乘除法优先级低,所以引入了括号来提高算术优先级:

 

 括号除了可以用来提升运算优先级,也可以用作强制类型转换,前面我们介绍了隐式类型转换,但是隐式类型转换存在局限性,比如此时我们希望将一个大的类型转换为一个小的类型:

强制类型转换存在一定的风险,比如: 

 

 解释:这里的128为 00000000 00000000 00000000 10000000 ->byte只有一个字节,所以说保留最后8位 :10000000 = -128

强制类型转换只有在明确不会出现问题的情况下,才可以使用。当然,强制类型转换也可以用在后面的类中。

优先级运算符结合性
1( )从左向右
2- + (强制类型转换)从右向左
3* / %从左向右
4+(加法,包括字符串) -(减法)从左向右
5=从右向左

自增自减运算符: 

自增自减是有两种形式的:b=++a或者b=a++,前者是先将a自增再赋值给b,后者是先将a赋值给b,然后a再自增。

自增自减运算符的优先级与正负号等价比如

所以先计算左右两边的式子++是右结合性所以左边式子是-8,a现在为9,所以右边式子是10,总体为2。

优先级运算符结合性
1( )从左向右
2- + (强制类型转换) ++ --从右向左
3* / %从左向右
4+(加法,包括字符串) -(减法)从左向右
5= += -= *= /= %=从右向左

位运算

位运算偏向于底层,直接使用位运算符以二进制形式操作目标,位运算符包括:&、|、^、~

按位与&

9 & 3 = 1 

实际上是        9:1001 3:0011 

        1001

&      0011

_____________

        0001 

同理,按位或,就是只要任意为1,那么结果就是1。

注意,按位异或符号初学者会以为是乘方运算,但是JAVA中并没有乘方符号,^是异或符号。

9 ^ 3 = 10 实际上就是:9:1001 3:0011

        1001

^       0011

________________

        1010                == 2^1 + 2 ^ 3 = 10

按位取反操作与之前的正负号一样,只操作一个数,最好理解,如果这一位上是1,变成0,如果是0,变成1:

  • 也就是127:0111 1111
  • ~128 = 1000 0000 = -128

除了以上四个运算符之外,还有位移运算符,比如:

1 << 2 = 4 => 0001 >> 2 = 0100 = 4,尾部使用0填充,此时就是4。发现规律:左移操作每进行一次,结果就会X2,所以说除了直接使用*进行乘2的运算之外,我们也可以使用左移操作来完成。        

同理:右移操作就是向右移动每一位。

8 >>2 =  2 => 1000 >> 2 = 0010 = 2,(头部使用符号位数字填充,此时是2),发现规律:右移操作可以快速进行除以2的计算。

        对于负数来说,左移和右移操作不会改变其符号位上的数字,符号位不受位移操作影响。

 

  •  也就是 -4 = 1111 1100
  • 1111 1100 >> 2 = 1111 1111 =>取反+1=原码 = -1

所以总结一下:

  • 左移操作<<:高位直接丢弃,低位补0
  • 右移操作>>:低位直接丢弃,符号位是什么就补什么

以上是有符号(不动符号位)的位移操作,>>>是无符号(符号位需要变)的位移操作 

最后我们还是来总结一下优先级:

优先级运算符结合性
1( )从左向右
2~ - + (强制类型转换) ++ --从右向左
3* / %从左向右
4+ -从左向右
5<< >> >>>从左向右
6&从左向右
7^从左向右
8|从左向右
9= += -= *= /= %= &= |= ^= <<= >>= >>>=从右向左

关系运算符:>= <= > < == 

关系短路: a = 150, b = 100 >= a && a >= 60

上来就不满足条件,此时因为&&为两个都为真才为真。 第一个为假,不再往后判断,总体为假。

总结:&&与运算是如果第一个条件为假,就不会往后判断了,不会影响结果

          ||或运算是如果第一个条件为真真,就不会往后判断了,不会影响结果。

public static void main(String[] args) {
    int a = 10;
    boolean b = a++ > 10 && ++a == 12;
    System.out.println("a = "+a + ", b = "+b);
}

解析:&&与运算就从左到右运算,有一个为假,就不会判断了,第一个条件a++>10,将a先与10相比,再让a自增1,a>10为假,则不会往后判断了。

优先级运算符结合性
1( )从左向右
2~ - + (强制类型转换) ++ --从右向左
3* / %从左向右
4+ -从左向右
5<< >> >>>从左向右
6> < >= >=从左向右
7== !=从左向右
8&从左向右
9^从左向右
10|从左向右
11&&从左向右
12||从左向右
13? :从右向左
14= += -= *= /= %= &= |= ^= <<= >>= >>>=从右向左

代码块与作用域

变量的使用范围,仅限于其定义时所处的代码块,一旦超出对应的代码块区域,那么就相当于没有这个变量了。最近的大括号。

流程控制语句

当然switch中可以继续嵌套其他的流程控制语句,比如if:

public static void main(String[] args) {
    char c = 'A';
    switch (c) {
        case 'A':
            if(c == 'A') {    //嵌套一个if语句
                System.out.println("表扬!");
            }
            break;
        case 'B':
            System.out.println("去平行班!");
            break;
    }
}

for循环语句 for(表达式1:表达式2:表达式3)循环体

表达式1:在循环开始时仅执行一次

表达式2:每次循环开始前会执行一次,要求为判断语句。用于判断是否进入循环,若为真,那么进入循环,否则结束循环。

表达式3:每次循环完成执行一次。

循环体:每次循环都会执行一次循环体。

break为结束循环,continue为加速循环,跳过当前循环。

虽然使用break和continue关键字能够更方便的控制循环,但是注意在多重循环嵌套下,它只对离它最近的循环生效(就近原则):

如果一个代码块中存在多个循环,那么直接对当前代码块的标记执行break时会直接跳出整个代码块:

 水仙花数

“水仙花数(Narcissistic number)也被称为超完全数字不变数(pluperfect digital invariant, PPDI)、自恋数、自幂数、阿姆斯壮数或阿姆斯特朗数(Armstrong number),水仙花数是指**一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。**例如:1^3 + 5^3+ 3^3 = 153。”

 

 九九乘法表:

 

类与对象

对象是某一类事物实际存在的每个个体,因而也被称为实例(instance)我们每个人都是人类的一个实际存在的个体。

我们同样可以使用一个变量来指代某个对象,只不过引用类型的变量,存储的是对象的引用,而不是对象本身。类似于指针差不多。

方法定义时编写的参数叫,形式参数,而调用方法实际传入的参数,我们称为实际参数。

return 之后的语句不可到达。

方法的构造与使用

这是一个大坑:确实是值传递,但是前面说引用类型的变量仅仅存放的是对象的引用,而不是对象本身。那么这里的值传递,相当于将对象的引用复制到了方法内部的变量中,而这个内部的变量,依然是引用的同一个对象,所以说这里在方法内操作,相当于直接操作外面的定义对象。

方法的重载:

一个类中包含多个重名的方法,但是需要的形式参数不一样,方法的返回类型,可以相同,可以不同,但是仅返回类型不同,是不允许的!

特别:

方法之间可以相互调用。如果说互相方法调用,你调用我,我调用你,无休无止的调用,会产生栈溢出。

构造方法

构造方法不需要填写返回值,并且方法名称和类名相同,默认情况下每个类都会自带一个没有任何参数的无参构造方法(不去写,编译会自带的),可以对他进行修改。

构造方法在new的时候就是在调用无参的构造方法。

在创建构造方法的时候,我们可以创建带参的构造方法:

我们自己定义的构造方法之后,会覆盖掉默认的那一个无参构造方法。

去看反编译的结果,会发现此时没有无参构造了,而是我们自己编写的。

如果不给他赋值的话,就会使用默认的初始值。

我们可以在类中添加代码块,代码块同样在对象构造之前进行,在变量初始化之后执行。 

执行顺序是:代码先初始化>>代码块>>构造方法

标准情况下还是通过构造方法进行进行对象初始化工作,所以说这里做了解就行了。

静态变量与静态方法:

成员变量:对象具有的属性,成员属性与成员方法与构造方法。

静态的内容:可以把它看做是属于这个类的,也可以理解为是所有对象共享的内容,我们可以使用关键字static关键字来声明一个变量或一个方法为静态的,那么通过这个类所创建的对象,操作的都是同一个目标,对象再多,一个对象改变了静态变量的值,那么其他对象读取的就是被改变的值。

所以说一般情况下,我们并不会通过一个具体的对象去修改和使用静态属性,而是通过这个类去使用:

静态方法同样是属于类的,而不是具体的某个对象,所以说,就像下面这样:

因为静态方法是属于类的,所以我们在静态方法中,无法获取成员变量,就好比说某个人的姓名是什么,没有说人类的姓名。

静态变量什么时候初始化呢?

实际上怎么执行?是将.class文件丢给jvm去执行,而每一个.class文件其实就是我们编写的一个类,我们在Java中使用一个类前jvm并不会在一开始就去加载它,而是在需要时才会去加载(优化)一般遇到以下情况时才会会加载类:

1.访问类的静态变量,或者为静态变量赋值

2.new创建类的实例(隐式加载)

3.调用类的静态方法

4.子类初始化时

5.反射介绍

所有被标记为静态的内容,会在类刚加载的时候就分配,而不是在对象创建的时候分配,所以说静态内容一定会在第一个对象初始化之前完成加载。

当然,如果我们压根就没有去使用这个类,那么也不会被初始化了。

包与访问控制

注意,在不同包下的类,即使类名相同,也是不同的两个类:

如:

由于默认导入了系统自带的String类,并且也导入了我们自己定义的String类,那么此时就出现了歧义,编译器也不知道我们想要用哪一个。

实际上,java就像我们的个人隐私一样,有自己的访问权限。

  • private 私有 标记为私有的内容无法被除了当前类以外的任何位置访问。
  • 什么都不写 默认,默认情况下,只能被类本身和同包中的其他类访问。
  • protected  受保护,标记为受保护的内容可以能被类本身同包中的其他类访问,也可以被子类访问(子类我们会在下一章介绍)
  • public 公共,标记为公共的内容,允许在任何地方被访问。
  • 这四种访问权限,总结如下表:

    当前类同一个包下的类不同包下的子类不同包下的类
    public
    protected
    默认
    private
    注意,我们创建的普通类不能是protected或是private权限,因为如果当前普通类是给当前的包内使用或者是给外面使用,如果用的是private,谁都不能用,为什么要创建呢。

封装

封装的目的是为了保证变量的安全性,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员,如果不进行封装

也就是说,外部现在只能通过调用我定义的方法来获取成员属性。

如果不允许外部使用new创建对象。

使用单例模式创建对象。

 另一种方式:

封装思想其实就是把实现细节给隐藏了,外部只需知道这个方法是什么作用,而无需关心实现,要用什么由类自己来做,不需要外面来操作类内部的东西去完成,封装就是通过访问权限控制来实现的。  

继承

在定义不同类的时候存在一些相同属性,为了方便可以使用这些共同属性抽象成一个父类,在定义其他子类时可以继承父类,减少代码重复性,子类可以使用父类非私有的成员。

实际上划分出来的类,本质上还是人类,也就是说人类具有的属性,这些划分出来的类同样具有,但是,这些划分出来的类也会拥有自己独特的技能。

是不是感觉非常人性化,子类继承了父类的全部能力,同时还可以扩展自己的独特能力,就像一句话说的: 龙生龙凤生凤,老鼠儿子会打洞。 

如果父类存在一个有参构造方法,子类必须在构造方法中调用:

 

强制类型转换

向下转型: 

 向上转型:

instanceof判断变量是否是所引用的对象。它的作用是判断其左边对象是否为其右边类的实例

 如果父类的name属性和子类的name属性是同时存在的,怎么去访问父类的呢?同样可以使用super关键字来表示父类。

 但是注意,没有super.super这种用法,也就是说如果存在多级继承的话,那么最多只能通过这种方法访问到父类的属性(包括继承下来的属性)。

顶层Object类

 所有的类都默认继承自Object类,除非手动指定继承的类型,但是依然改变不了顶层的父类是Object类。

public class Object {

    private static native void registerNatives();   //标记为native的方法是本地方法,底层是由C++实现的
    static {
        registerNatives();   //这个类在初始化时会对类中其他本地方法进行注册,本地方法不是我们SE中需要学习的内容,我们会在JVM篇视频教程中进行介绍
    }

    //获取当前的类型Class对象,这个我们会在最后一章的反射中进行讲解,目前暂时不会用到
    public final native Class<?> getClass();

    //获取对象的哈希值,我们会在第五章集合类中使用到,目前各位小伙伴就暂时理解为会返回对象存放的内存地址
    public native int hashCode();

  	//判断当前对象和给定对象是否相等,默认实现是直接用等号判断,也就是直接判断是否为同一个对象
  	public boolean equals(Object obj) {
        return (this == obj);
    }
  
    //克隆当前对象,可以将复制一个完全一样的对象出来,包括对象的各个属性
    protected native Object clone() throws CloneNotSupportedException;

    //将当前对象转换为String的形式,默认情况下格式为 完整类名@十六进制哈希值
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    //唤醒一个等待当前对象锁的线程,有关锁的内容,我们会在第六章多线程部分中讲解,目前暂时不会用到
    public final native void notify();

    //唤醒所有等待当前对象锁的线程,同上
    public final native void notifyAll();

    //使得持有当前对象锁的线程进入等待状态,同上
    public final native void wait(long timeout) throws InterruptedException;

    //同上
    public final void wait(long timeout, int nanos) throws InterruptedException {
        ...
    }

    //同上
    public final void wait() throws InterruptedException {
        ...
    }

    //当对象被判定为已经不再使用的“垃圾”时,在回收之前,会由JVM来调用一次此方法进行资源释放之类的操作,这同样不是SE中需要学习的内容,这个方法我们会在JVM篇视频教程中详细介绍,目前暂时不会用到
    protected void finalize() throws Throwable { }
}

方法的重写 

重写Object的equals方法,就会按照我们的方式进行判断了。

为了方便查看对象的各个属性,我们可以将Object类提供的toString方法重写了:

 静态方法不支持重写,因为它属于类本身,但是它可以被继承

 基于这种方法可以重写的特性,对于一个类定义的行为,不同的子类可以出现不同的行为,比如考试,学生考试可以得到A,而工人去考试只能得到D:

如果我们不不希望子类重写某个方法,我们可以在方法前添加final关键字,表示这个方法已经是最终形态了。

或者,如果父类中方法的可见性为private,那么子类同样无法访问,也就不能重写,但是可以定义同名方法,虽然可以编译通过,但这并不是对父类的重写,而仅仅是子类自己创建的一个方法。

访问权限问题,子类在重写父类方法时候,不能降低父类方法中的可见性。

 可以看到作为子类时就可以正常调用,但是如果将其作为父类使用,因为访问权限不足所有就无法使用,总之,子类重写的方法权限不能比父类还低。

抽象类

越是处于顶层定义的类,实际上可以进一步地进行抽象,比如我们前面编写的考试方法:

比如:人考试是一个抽象概念,而学生和工人怎么考试,才是一个具体的实现,父类中不需要提供实现。

把人类变成抽象类,抽象类比类还要抽象:

我们把人变成抽象类,方法exam也变成抽象方法,具体实现在它继承的的子类中具体实现。

 接口

1.接口包含了一些类方法的定义,类可以实现这个接口。(类似于一个插件,只能作为一个附加属性加载到主体上,具体实现还需要由主体来实现),对于人类说,学生和老师他们都具有学习这个能力,那么可以将学习这个能力抽象成接口来进行使用,只要实现这个接口的类,都要实现学习这个方法。

认为:有人说接口是Java的多继承,但我个人认为错的,实际上实现接口更像是一个类的功能列表出现,作为附加功能存在。

接口跟抽象类一样,不能直接创建对象,但是我们也可以将接口实现类的对象以接口的形式去使用:

接口同样支持向下转型:

如果方法在接口中存在默认实现,那么实现类中不强制要求进行实现。

 接口不同于类,接口中不允许存在成员变量和成员方法,但是可以存在静态变量和静态方法,在接口中定义的变量只能是:

public interface Study {
    public static final int a = 10;   //接口中定义的静态变量只能是public static final的
  
  	public static void test(){    //接口中定义的静态方法也只能是public的
        System.out.println("我是静态方法");
    }
    
    void study();
}

跟普通的类一样,我们可以直接通过接口名.的方式使用静态内容:

public static void main(String[] args) {
    System.out.println(Study.a);
    Study.test();
}

最后我们来介绍一下Object类中提供的克隆方法,为啥要留到这里才来讲呢?因为它需要实现接口才可以使用:

子类继承父类,需要实现Cloneable,需要重写clone方法

类重写的方法的优先级大于接口。 

枚举类

//这里使用javap命令对class文件进行反编译得到 Compiled from "Status.java"
public final class com.test.Status extends java.lang.Enum<com.test.Status> {
  public static final com.test.Status RUNNING;
  public static final com.test.Status STUDY;
  public static final com.test.Status SLEEP;
  public static com.test.Status[] values();
  public static com.test.Status valueOf(java.lang.String);
  static {};
}

 包装类

 装箱

拆箱

 因为a和b是两个不同的引用,所以其地址是不同的,所以是false

如果超过127的范围,就会得到不同的对象了。 

可以对8,16进制数转换为10进制数。 

10进制转换为8或16进制

特殊包装类BigInteger

public static void main(String[] args) {
    BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);    //表示Long的最大值,轻轻松松
    System.out.println(i);
}

我们可以通过调用类中的方法,进行运算操作:

public static void main(String[] args) {
    BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
    i = i.multiply(BigInteger.valueOf(Long.MAX_VALUE));   //即使是long的最大值乘以long的最大值,也能给你算出来
    System.out.println(i);
}

我们来看看结果:

image-20220922211414392

可以看到,此时数值已经非常大了,也可以轻松计算出来。咱们来点更刺激的:

public static void main(String[] args) {
    BigInteger i = BigInteger.valueOf(Long.MAX_VALUE);
    i = i.pow(100);   //long的最大值来个100次方吧
    System.out.println(i);
}

可以看到,这个数字已经大到一排显示不下了:

image-20220922211651719

一般情况,对于非常大的整数计算,我们就可以使用BigInteger来完成。

我们接着来看第二种,前面我们说了,浮点类型精度有限,对于需要精确计算的场景,就没办法了,而BigDecimal可以实现小数的精确计算。

public static void main(String[] args) {
    BigDecimal i = BigDecimal.valueOf(10);
    i = i.divide(BigDecimal.valueOf(3), 100, RoundingMode.CEILING);
  	//计算10/3的结果,精确到小数点后100位
  	//RoundingMode是舍入模式,就是精确到最后一位时,该怎么处理,这里CEILING表示向上取整
    System.out.println(i);
}

可以看到,确实可以精确到这种程度:

image-20220922212222762

但是注意,对于这种结果没有终点的,无限循环的小数,我们必须要限制长度,否则会出现异常。

一维数组    

数组是相同类型数据的有序集合,数组可以代表任何相同类型的一组内容(包括引用类型和基本类型)其中存放的每一个数据称为数组的一个元素,我们来看看如何去定义一个数组变量:

public static void main(String[] args) {
    int[] array;   //类型[]就表示这个是一个数组类型
}

注意,数组类型比较特殊,它本身也是类,但是编程不可见(底层C++写的,在运行时动态创建)即使是基本类型的数组,也是以对象的形式存在的,并不是基本数据类型。所以,我们要创建一个数组,同样需要使用new关键字:

public static void main(String[] args) {
    int[] array = new int[10];   //在创建数组时,需要指定数组长度,也就是可以容纳多个int变量的值
  	Object obj = array;   //因为同样是类,肯定是继承自Object的,所以说可以直接向上转型
}

除了上面这种方式之外,我们也可以使用其他方式:

类型[] 变量名称 = new 类型[数组大小];
类型 变量名称[] = new 类型[数组大小];  //支持C语言样式,但不推荐!

类型[] 变量名称 = new 类型[]{...};  //静态初始化(直接指定值和大小)
类型[] 变量名称 = {...};   //同上,但是只能在定义时赋值

创建出来的数组每个位置上都有默认值,如果是引用类型,就是null,如果是基本数据类型,就是0,或者是false,跟对象成员变量的默认值是一样的,要访问数组的某一个元素,我们可以:

public static void main(String[] args) {
    int[] array = new int[10];
    System.out.println("数组的第一个元素为:"+array[0]);  //使用 变量名[下标] 的方式访问
}

注意,数组的下标是从0开始的,不是从1开始的,所以说第一个元素的下标就是0,我们要访问第一个元素,那么直接输入0就行了,但是注意千万别写成负数或是超出范围了,否则会出现异常。

我们也可以使用这种方式为数组的元素赋值:

public static void main(String[] args) {
    int[] array = new int[10];
    array[0] = 888;   //就像使用变量一样,是可以放在赋值运算符左边的,我们可以直接给对应下标位置的元素赋值
    System.out.println("数组的第一个元素为:"+array[0]);
}

因为数组本身也是一个对象,数组对象也是具有属性的,比如长度:

public static void main(String[] args) {
    int[] array = new int[10];
    System.out.println("当前数组长度为:"+array.length);   //length属性是int类型的值,表示当前数组长度,长度是在一开始创建数组的时候就确定好的
}

注意,这个length是在一开始就确定的,而且是final类型的,不允许进行修改,也就是说数组的长度一旦确定,不能随便进行修改,如果需要使用更大的数组,只能重新创建。

当然,既然是类型,那么肯定也是继承自Object类的:

public static void main(String[] args) {
    int[] array = new int[10];
    System.out.println(array.toString());
    System.out.println(array.equals(array));
}

但是,很遗憾,除了clone()之外,这些方法并没有被重写,也就是说依然是采用的Object中的默认实现:

image-20220922220403391

所以说通过toString()打印出来的结果,好丑,只不过我们可以发现,数组类型的类名很奇怪,是[开头的。

因此,如果我们要打印整个数组中所有的元素,得一个一个访问:

public static void main(String[] args) {
    int[] array = new int[10];
    for (int i = 0; i < array.length; i++) {
        System.out.print(array[i] + " ");
    }
}

有时候为了方便,我们可以使用简化版的for语句foreach语法来遍历数组中的每一个元素:

public static void main(String[] args) {
    int[] array = new int[10];
    for (int i : array) {    //int i就是每一个数组中的元素,array就是我们要遍历的数组
        System.out.print(i+" ");   //每一轮循环,i都会更新成数组中下一个元素
    }
}

是不是感觉这种写法更加简洁?只不过这仅仅是语法糖而已,编译之后依然是跟上面一样老老实实在遍历的:

public static void main(String[] args) {   //反编译的结果
    int[] array = new int[10];
    int[] var2 = array;
    int var3 = array.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        int i = var2[var4];
        System.out.print(i + " ");
    }

}

对于这种普通的数组,其实使用还是挺简单的。这里需要特别说一下,对于基本类型的数组来说,是不支持自动装箱和拆箱的:

public static void main(String[] args) {
    int[] arr = new int[10];
    Integer[] test = arr;
}

还有,由于基本数据类型和引用类型不同,所以说int类型的数组时不能被Object类型的数组变量接收的:

image-20220924114859252

但是如果是引用类型的话,是可以的:

public static void main(String[] args) {
    String[] arr = new String[10];
    Object[] array = arr;    //数组同样支持向上转型
}
public static void main(String[] args) {
    Object[] arr = new Object[10];
    String[] array = (String[]) arr;   //不支持 

    //要这样
    Object[] arr = new String[10];
    String[] array = (String[]) arr;   //不支持
}

多维数组 

存放数组的数组,相当于将维度进行了提升,比如上面的就是一个2x10的数组:

image-20220922221557130

可变长参数

我们接着来看数组的延伸应用,实际上我们的方法是支持可变长参数的,什么是可变长参数?

可变长数组是指,可以传入0-N个对应类型的实参。

注意,如果同时存在其他参数,那么可变长参数只能放在最后而且只能有一个: 

字符串:

 创建字符串的两个方式

public static void main(String[] args) {
    String str1 = "Hello World";
    String str2 = "Hello World";
    System.out.println(str1 == str2);
}

 true

public static void main(String[] args) {
    String str1 = new String("Hello World");
    String str2 = new String("Hello World");
    System.out.println(str1 == str2);
}

false

 因此,如果我们仅仅要判断两个字符串的内容是否相等不要用==,String类重载了equals方法用于判断内容相同。

字符数组和字符串之间是可以快速进行相互转换的:

public static void main(String[] args) {
    String str = "Hello World";
    char[] chars = str.toCharArray();
    System.out.println(chars);
}

 其他用法 大小写方法 str.lowerCase toUperCase startsWith indexOf

StringBuilder类

    StringBuilder builder = new StringBuilder();   //一开始创建时,内部什么都没有
        builder.append("AAA");   //我们可以使用append方法来讲字符串拼接到后面
        builder.append("BBB");
        System.out.println(builder);   //当我们字符串编辑完成之后,就可以使用toString转换为字符串了

builder.replace替换某个字符  builder.reverse()反序

链式调用

正则表达式:

用于规定给定组件必须要出现多少次才能满足匹配的,我们一般称为限定符,限定符表如下:

字符描述
*匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。***** 等价于 {0,}
+匹配前面的子表达式一次或多次。例如,zo+ 能匹配 "zo" 以及 "zoo",但不能匹配 "z"+ 等价于 {1,}
?匹配前面的子表达式零次或一次。例如,do(es)? 可以匹配 "do" 、 "does"、 "doxy" 中的 "do" 。? 等价于 {0,1}
{n}n 是一个非负整数。匹配确定的 n 次。例如,o{2} 不能匹配 "Bob" 中的 o,但是能匹配 "food" 中的两个 o
{n,}n 是一个非负整数。至少匹配n 次。例如,o{2,} 不能匹配 "Bob" 中的 o,但能匹配 "foooood" 中的所有 oo{1,} 等价于 o+o{0,} 则等价于 o*
{n,m}m 和 n 均为非负整数,其中 n <= m。最少匹配 n 次且最多匹配 m 次。例如,o{1,3} 将匹配 "fooooood" 中的前三个 oo{0,1} 等价于 o?。请注意在逗号和两个数之间不能有空格。

如果我们想要表示一个范围内的字符,可以使用方括号:

public static void main(String[] args) {
    String str = "abcabccaa";
    System.out.println(str.matches("[abc]*"));   //表示abc这几个字符可以出现 0 - N 次
}

对于普通字符来说,我们可以下面的方式实现多种字符匹配:

字符描述
[ABC]匹配 [...] 中的所有字符,例如 [aeiou] 匹配字符串 "google runoob taobao" 中所有的 e o u a 字母。
[^ABC]匹配除了 [...] 中字符的所有字符,例如 [^aeiou] 匹配字符串 "google runoob taobao" 中除了 e o u a 字母的所有字母。
[A-Z][A-Z] 表示一个区间,匹配所有大写字母,[a-z] 表示所有小写字母。
.匹配除换行符(\n、\r)之外的任何单个字符,相等于 [^\n\r]
[\s\S]匹配所有。\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行。
\w匹配字母、数字、下划线。等价于 [A-Za-z0-9_]

 正则表达式 Yes

成员内部类

这样子定义:

 使用成员内部类

这里我们需要特别注意一下,在成员内部类中,是可以访问到外层的变量的:

public class Test {
    private final String name;
    
    public Test(String name){
        this.name = name;
    }
    public class Inner {
        public void test(){
            System.out.println("我是成员内部类:"+name);
         		//成员内部类可以访问到外部的成员变量
          	//因为成员内部类本身就是某个对象所有的,每个对象都有这样的一个类定义,这里的name是其所依附对象的
        }
    }
}
public static void main(String[] args) {
    Test a = new Test("小明");
    Test.Inner inner1 = a.new Inner();   //依附于a创建的对象,那么就是a的
    inner1.test();

    Test b = new Test("小红");
    Test.Inner inner2 = b.new Inner();  //依附于b创建的对象,那么就是b的
    inner2.test();
}

那么如果内部类中也定义了同名的变量,此时我们怎么去明确要使用的是哪一个呢?

public class Test {
    private final String name;

    public Test(String name){
        this.name = name;
    }
    public class Inner {

        String name;
        public void test(String name){
            System.out.println("方法参数的name = "+name);    //依然是就近原则,最近的是参数,那就是参数了
            System.out.println("成员内部类的name = "+this.name);   //在内部类中使用this关键字,只能表示内部类对象
            System.out.println("成员内部类的name = "+Test.this.name);
          	//如果需要指定为外部的对象,那么需要在前面添加外部类型名称
        }
    }
}

 包括对方法的调用和super关键字的使用,也是一样的:

public class Inner { String name; public void test(String name){ this.toString(); //内部类自己的toString方法 super.toString(); //内部类父类的toString方法 Test.this.toString(); //外部类的toSrting方法 Test.super.toString(); //外部类父类的toString方法 } }

静态内部类 

局部内部类;

 局部内部类就像局部变量一样,可以在方法中定义。

public class Test {
    public void hello(){
        class Inner{   //局部内部类跟局部变量一样,先声明后使用
            public void test(){
                System.out.println("我是局部内部类");
            }
        }
        
        Inner inner = new Inner();   //局部内部类直接使用类名就行
        inner.test();
    }
}

匿名内部类:

普通类生成匿名内部类的方法:

当然,并不是说只有抽象类和接口才可以像这样创建匿名内部类,普通的类也可以,只不过意义不大,一般情况下只是为了进行一些额外的初始化工作而已。

 Lambda表达式

如果一个接口中有且只有一个待实现的抽象方法,那么我们可以将匿名内部类简写为Lambda表达式。

如果有一个参数和返回值的话:

如果方法体中只有一个返回语句,可以直接省略花括号和return关键字,简洁版本。

Lambda使用变量要有final作为前缀,变量要一成不变的

方法引用

方法引用就是将一个已实现的方法,直接作为接口中抽象方法的实现(当然前提是方法定义得一样才行)

我们发现,Integer.sum的参数和返回值,跟我们在Study中定义的完全一样,所以说我们可以直接使用方法引用:

public static void main(String[] args) {
    Study study = Integer::sum;    //使用双冒号来进行方法引用,静态方法使用 类名::方法名 的形式
    System.out.println(study.sum(10, 20));
}

方法引用其实本质上就相当于将其他方法的实现,直接作为接口中抽象方法的实现。任何方法都可以通过方法引用作为实现:

public interface Study {
    String study();
}

如果是普通从成员方法,我们同样需要使用对象来进行方法引用:

public static void main(String[] args) {
    Main main = new Main();
    Study study = main::lbwnb;   //成员方法因为需要具体对象使用,所以说只能使用 对象::方法名 的形式
}

public String lbwnb(){
    return "卡布奇诺今犹在,不见当年倒茶人。";
}

因为现在只需要一个String类型的返回值,由于String的构造方法在创建对象时也会得到一个String类型的结果,所以说:

public static void main(String[] args) {
    Study study = String::new;    //没错,构造方法也可以被引用,使用new表示
}

反正只要是符合接口中方法的定义的,都可以直接进行方法引用,对于Lambda表达式和方法引用,在Java新特性介绍篇视频教程中还有详细的讲解,这里就不多说了。

异常机制

运算异常

 运行时异常

 编译时异常

错误: 

public static void main(String[] args) {
    test();
}

private static void test(){
    test();
}

抛出异常 

自定义异常

 注意,如果我们在方法中抛出了一个非运行时异常,那么必须告知函数的调用方我们会抛出某个异常,函数调用方必须要对抛出的这个异常进行对应的处理才可以:

 

不同的异常抛出都需要注明:

private static void test(int a) throws FileNotFoundException, ClassNotFoundException {  //多个异常使用逗号隔开
    if(a == 1)
        throw new FileNotFoundException();
    else 
        throw new ClassNotFoundException();
}

异常的处理:

 出现异常的时候我们希望能够自己处理出现的问题,让程序运行下去,需要对异常捕获:

 

使用try-catch,只要是这个范围内发生的异常,都可以被捕获,这里捕获的是NullPointerException空指针异常。

注意,catch中捕获的类型只能是Throwable的子类,也就是说要么是抛出的异常,要么是错误,不能是其他的任何类型。

如果某个方法明确指出会抛出哪些异常,除非抛出的异常是一个运行时异常,否则我们必须要使用try-catch语句块进行异常捕获,不然无法通过编译。

数组越界异常是运行时异常的子类,所以可以捕获到。

代码可能出现多种类型的异常时,希望分不同情况处理不同的异常,可以使用多重异常捕获。

断言表达式:

我们可以使用断言表达式来对某些东西进行判断,如果判断失败会抛出错误。

数学工具类,与数组工具类。 

实战训练,冒泡排序

二分查找

青蛙跳台阶问题

递归程序设计

回文串判断 

 汉诺塔

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值