Java基础篇中

234 阅读9分钟

开篇先放下个人的Github地址:masuo777.github.io/ 欢迎访问!!!

运算

参数传递

在Java中,参数是以值传递的方式传入方法中,而不是引用传递。

package JavaBase;

/**
 * @author masuo
 * @create 2021/7/8 23:01
 * @Description java中的参数传递 是值传递
 */
public class ParmeterPass {


    public static void main(String[] args) {
        // 此时的dog是一个指针,存储对象的地址,
        // 在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。
        Dog doga = new Dog("doga");
        changeDogName(doga);
        System.out.println(doga.getName());//dogb

        System.out.println(doga.getAddress());//JavaBase.Dog@1b6d3586
        changeDogAddress(doga);
        System.out.println(doga.getName());//dogb,这里名称没变是因为方法中的对象对此处无影响,因为没有返回赋值的操作
        System.out.println(doga.getAddress());//JavaBase.Dog@1b6d3586,与上面相同,在方法中改变对象,对此处无影响
    }

    private static void changeDogAddress(Dog doga) {
        System.out.println(doga.getAddress());//JavaBase.Dog@1b6d3586,在这里还与上面的地址相同,因为地址还没发生改变
        doga = new Dog("dogc");//生成新的dog对象,
        System.out.println(doga.getName());//dogc
        System.out.println(doga.getAddress());//JavaBase.Dog@4554617c,在这里地址就已经发生了改变
    }

    private static void changeDogName(Dog doga) {
        //在此方法中,修改对象的字段值会修改元对象字段值,因为此时引用的是同一个对象
        doga.setName("dogb");
    }


}


class Dog{
    String name;

    Dog(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public String getAddress(){
        return super.toString();
    }
}

向下转型(隐式)

在Java中,是不允许隐式向下转型操作的,这样会使精度降低。

float与double

float的精度是低于double的,在Java的基础类型中了解到,float是32位,double是64位的,由于存在小于0的情况,所以两个浮点类型都含有一个符号位,1位负数,0位正数,忽略不计。剩余位数中,float有8位指数,23位尾数(小数点之后的位数),double有11位指数,52位尾数。

在挑选使用哪种类型存储时需要从存储与精度中做选择,如果精度重要,则选择double,如果存储更重要,则选择float。

声明

在声明float类型时,需要加上f后缀,来表明这是一个低精度类型,否则会报错(Incompatible types. Found: 'double', required: 'float')。

private static void floatAndDouble() {
    //直接将1.1赋值给float是隐式向下转型,是不可取的。
    float f = 1.1;//Incompatible types. Found: 'double', required: 'float'
    float f = 1.1f;
    double d = 1.1;
}

short 与int,int与long.

private static void shortAndInt() {
    int i = 1;
    short s = i;//Incompatible types. Found: 'int', required: 'short',类型不符合/不兼容
}
private static void intAndLong() {
    long l = 1;
    int i = l;//Incompatible types. Found: 'long', required: 'int',
}

逻辑运算符

||,&&为逻辑运算符

&&(与):如果两操作数中含有一个假则为假,

||(或):如果两操作数中含一个真则为真,

运算符

&、|、^、~、>>、<<、<<<、>>>为位运算符,其中三个左大于,右大于是无符号运算。

&:按位与操作,有0则为0,

0&00
0&10
1&00
1&11

|:按位或操作,有1则为1

0|00
0|11
1|01
1|11

Switch

从Java7开始,Switch支持在判断语句使用String类型,不支持long,float,double,当然如果条件过于复杂还是弃暗(switch)投明(if)比较好。

private static void switchAfterJava7() {
    String s1 = "1";
    switch (s1){
        case "1":
            System.out.println(s1);
        case "2":
            System.out.println(s1);
    }
}

关键字

比较重要且常见的关键字有final,static,

Java中所有的关键字:

image-20210708235734472

关键字含义
abstract表明类或者成员方法具有抽象属性
assert断言,用来进行程序调试
boolean基本数据类型之一,声明布尔类型的关键字
break提前跳出一个块
byte基本数据类型之一,字节类型
case用在switch语句之中,表示其中的一个分支
catch用在异常处理中,用来捕捉异常
char基本数据类型之一,字符类型
class声明一个类
const保留关键字,没有具体含义
continue回到一个块的开始处
default默认,例如,用在switch语句中,表明一个默认的分支。Java8 中也作用于声明接口函数的默认实现
do用在do-while循环结构中
double基本数据类型之一,双精度浮点数类型
else用在条件语句中,表明当条件不成立时的分支
enum枚举
extends表明一个类型是另一个类型的子类型。对于类,可以是另一个类或者抽象类;对于接口,可以是另一个接口
final用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变,用来定义常量
finally用于处理异常情况,用来声明一个基本肯定会被执行到的语句块
float基本数据类型之一,单精度浮点数类型
for一种循环结构的引导词
goto保留关键字,没有具体含义
if条件语句的引导词
implements表明一个类实现了给定的接口
import表明要访问指定的类或包
instance of用来测试一个对象是否是指定类型的实例对象
int基本数据类型之一,整数类型
interface接口
long基本数据类型之一,长整数类型
native用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的
new用来创建新实例对象
package
private一种访问控制方式:私用模式
protected一种访问控制方式:保护模式
public一种访问控制方式:共用模式
return从成员方法中返回数据
short基本数据类型之一,短整数类型
static表明具有静态属性
strictfp用来声明FP_strict(单精度或双精度浮点数)表达式遵循[IEEE 754](baike.baidu.com/item/IEEE 754)算术规范
super表明当前对象的父类型的引用或者父类型的构造方法
switch分支语句结构的引导词
synchronized表明一段代码需要同步执行
this指向当前实例对象的引用
throw抛出一个异常
throws声明在当前定义的成员方法中所有需要抛出的异常
transient声明不用序列化的成员域
try尝试一个可能抛出异常的程序块
void声明当前成员方法没有返回值
volatile表明两个或者多个变量必须同步地发生变化
while用在循环结构中

接下来说一下几个经常用到的,却又是大家(我)不了解的,搞不清楚,稀里糊涂的那些,面试时还经常会问到的。

final

final我们都知道的这是一个关键字,用来声明那些不会变的常量,其他的迷迷糊糊的就不太清楚了,可能有印象,但是具体到某一个方法,或者类上的时候,就不了解了。这就是我写这篇博客的原因,巩固自己的基础。

① 数据

声明的数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。

​ 1)对于基本类型,final使数值不变;

​ 2)对于引用类型,final使引用不变,就是引用其他对象,但是被引用的对象本身可以修改。

private static void finalOnData() {

    //基本类型
    final int num1 = 10;
    // num1 = 2;//Cannot assign a value to final variable 'num1',意思是不能给一个final变量分配值

    //引用类型
    final Dog dog = new Dog("doga");
    System.out.println(dog.getName());//doga
    dog.setName("dogb");
    System.out.println(dog.getName());//dogb
    // dog = new Dog("dagc");//Cannot assign a value to final variable 'dog'
    
    //可以看到,对于基本类型,不能改变基本类型变量的值,对于引用类型,不能引用其他对象
}

② 方法

声明的方法不能被子类重写。private声明的函数被隐式的声明为final类型,因此private类也是不可重写的。当子类中有与父类中的private方法名相同的方法时,这是子类自己的私有方法,与父类私有方法无关,private不像final关键字一样,final声明的方法不可以在子类中存在同名方法,名称相同就不可以,而private运行存在相同的方法名。

class Dog{
    String name;

    Dog(String name){
        this.name = name;
    }

    final void finalDog(){
        System.out.println("这是final方法,不能被重写!");
    }
}

class DogA extends Dog{

    DogA(String name) {
        super(name);
    }

    void finalDog() {
        //'finalDog()' cannot override 'finalDog()' in 'JavaBase.Dog'; overridden method is final
    }
}

③ 类

最具代表的final类便是基础类型的封装类,如String类,被final声明的类不可以被继承。

public class FinalType extends String
//Cannot inherit from final 'java.lang.String'
//不能继承final String

public final class String

static

① 静态变量

  • 静态变量:又称为类变量,也就是说这个变量是属于类的,类的所有实例都共享变量,所以在内存中只存在一份静态变量,可以直接通过类名来访问,也可以通过实例对象来访问(不推荐)。静态变量属于静态存储方式,静态变量不代表值不改变,她的值是可以改变的,但是改了之后就不能回到之前的初始值了。

    JVM只会为静态变量分配一次内存空间,在类加载的时候完成静态变量的内存加载。

    静态存储:在程序运行期间分配固定的存储空间。静态存储不一定是静态变量,但是静态变量是静态存储。静态存储需要加上static才能成为静态变量。

  • 实例变量:每创建一个实例就会产生一个实例变量,他于该实例同生共死。

/**
 * @author masuo
 * @create 2021/7/10 9:36
 * @Description static 的使用
 */
public class StaticType {

    private static int s;

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

    /**
     * static对数据的使用
     * 1、静态变量
     * 2、实例变量
     */
    private static void staticOnData() {
        //在声明的地方打个断点,在没有执行这一步之前,就已经存在static变量了,
        // 即在调用函数之前static变量就已经存在了,说明静态变量在类加载的时候就存在了,
        StaticClassOne sco = new StaticClassOne();
      
        //在执行完声明之后,实例变量x被初始化,
        // 在这里,0是系统默认赋值的,
        int x = sco.x;//0
        int y = StaticClassOne.y;
        System.out.println("没有改变之前的StaticClassOne.y的值为:"+y);

        //改变静态变量
        for (int i = 0; i < 10; i++) {
            StaticClassOne.y = StaticClassOne.y+i;
            System.out.println("改变之后的StaticClassOne.y值为:"+StaticClassOne.y);
        }
    }

}

class StaticClassOne{
    public int x; //实例变量
    public static int y = 1; //静态变量
}

② 静态方法

同静态变量一样,静态方法在类加载的时候就已经存在了,在类加载的时候就存在的都不会依赖实例对象。所以静态方法必须是完整的,不依赖实例对象的存在,要有自己的实现,不能是抽象方法。(抽象与接口的区别在下面会讲到)。

静态方法只能访问该类的静态变量和静态方法。

方法中不能含有this和super等关键字,因为这都是和实体类相关联的。

/**
 * @author masuo
 * @create 2021/7/10 16:09
 * @Description 静态方法测试
 */
public abstract class StaticClassFun {
    public abstract static void get();//Illegal combination of modifiers: 'abstract' and 'static',不合法的修饰符组合
    public abstract void getNO();//正确
}

//访问静态变量和访问静态方法
public static void get(){

    System.out.println(y);//Non-static field 'y' cannot be referenced from a static context,非静态变量不可被静态方法引用
    System.out.println(x);

    get();
    getX();
    getAll();//Non-static method 'getAll()' cannot be referenced from a static context,非静态方法不可被静态方法引用
}

③ 静态语句块

静态语句块,在类初始化时运行一次,根据Java的类加载机制,类只在需要时被加载,且一次运行只加载一次,在初始化之后,类就存在某个地方(现在还不知道啊,还有好多要学啊,路还很长,只知道她大概是在JVM的内存中,具体是运行时方法区内存区,还是堆【不太可能】或者是别的地方,对于这一块了解的是真的很少。)

/**
 * @author masuo
 * @create 2021/7/10 16:09
 * @Description 静态方法测试
 */
public abstract class StaticClassFun {
    static {
        System.out.println("这是静态语句块,你只会看到我出现一次哦!");
    }

    public static void main(String[] args) {
        //在这里我们声明两个StaticClassOne类的实例对象,
        StaticClassOne sco1 = new StaticClassOne();
        StaticClassOne sco2 = new StaticClassOne();
        
        //至于他只会加载一次的原因,看完以上两个静态变量以及静态方法的描述之后,多少应该会有一点自己的理解吧,在这里类只会加载一次,第二次声明的时候,因为类的pool中已经含有这个类的引用了,所以不会再对这个类进行初始化。
    }
}

image-20210710185119594

④ 静态内部类

同以上静态变量,静态方法一样,静态内部类是不依赖于外部类的,而非静态内部类是需要依赖于外部类的实例对象的,非静态内部类需要先创建实例对象才能创建非静态内部类。

/**
 * @author masuo
 * @create 2021/7/11 12:58
 * @Description 静态内部类与非静态内部类
 */
public class StaticOuterClass {
    class NonStaticInnerClass{

    }

    static class StaticInnerClass{
		//静态内部类不能访问外部类的非静态变量和方法。
    }

    public static void main(String[] args) {
        //非静态内部类不能单独声明
        //NonStaticInnerClass nsic = new NonStaticInnerClass();//'JavaBase.StaticOuterClass.this' cannot be referenced from a static context,该类不能从静态上下文引用
        
        //静态内部类可以不依赖实例对象直接声明
        StaticInnerClass sic = new StaticInnerClass();

        //声明外部类的实例对象
        StaticOuterClass soc = new StaticOuterClass();

        //利用外部类的实例对象声明非静态内部类
        NonStaticInnerClass nsic = soc.new NonStaticInnerClass();

    }
}

⑤ 静态导包

静态导包的作用是简化代码,具体参考:blog.csdn.net/u012338954/…

⑥ 初始化顺序

到了最重要的地方,这里一定要认真仔细的看,去理解,上面已经讲得说的很细了(在我看来)。

首先,静态变量具有很高的优先级,静态变量与静态语句块取决于他们在代码中的顺序。

在这里需要提一下一个经典的问题,Java中类加载的问题!!!

例:引用于:blog.csdn.net/zfx2013/art…

/**
 * @author masuo
 * @create 2021/7/11 22:34
 * @Description java类的初始化循序
 */
public class JavaClassInitSequence {


    /** 输出顺序为
     *  1.父类静态(变量或者代码块)显式赋值,
     *  2.子类静态(变量或者代码块)显式赋值,
     *  3.父类非静态显式赋值(变量或代码块)
     *  4.父类构造函数
     *  5.子类非静态显式赋值(变量或代码块)
     *  6.子类构造函数
     *
     *  ***注意这里是显式赋值,首先必须是赋值语句,如果没有赋值语句,只是单纯的声明变量/或初始化变量,则不会输出,看注释1
     */
    public static void main(String[] args) {
        Cat d = new Cat();
        //①父类静态fm,fa.②子类静态ss,sm.③父类非静态ft,fn,a.④子类非静态sa,st,sd
        System.out.println("000");
        Cat o = new Cat();
        //
    }


}
class Animal{
    private int i = test();
    private static int j = method();
    //注释1:下面两行代码不会输出,因为他们不是显式赋值语句,隐式赋值即implicit(x)
    private static int x;
    


    static {
        System.out.println("fa");
    }
    Animal(){
        System.out.println("a");
    }
    {
        System.out.println("fn");
    }

    private int test() {
        System.out.println("ft");
        return 1;
    }

    private static int method() {
        System.out.println("fm");
        return 1;
    }

    private int implicit(int i) {
        System.out.println("ft");
        return i;
    }
}

class Cat extends Animal{
    {
        System.out.println("sa");
    }
    private int i = test();
    static {
        System.out.println("ss");
    }
    private static int j = method();

    Cat(){
        System.out.println("sd");
    }
    public int test(){
        System.out.println("st");
        return 1;
    }
    public static int method(){
        System.out.println("sm");
        return 1;
    }
}

输出顺序为 1.父类静态(变量或者代码块)显式赋值, 2.子类静态(变量或者代码块)显式赋值, 3.父类非静态显式赋值(变量或代码块) 4.父类构造函数 5.子类非静态显式赋值(变量或代码块) 6.子类构造函数

instanceof

参考:www.cnblogs.com/ysocean/p/8…

boolean result = obj instanceof Class

概述:根据定义来看,可以说instanceof是一个Java的双目运算符,双目运算符就是需要两个变量才能运算的运算符,可以想象为==,用来测试左侧对象是否是右侧类的实例。

List<String> ls = new ArrayList<>();
System.out.println(ls.getClass());//class java.util.ArrayList
System.out.println(ls instanceof Collection);//true

//在这里,ls是ArrayList类,ArrayList是List的接口实现类,List是Collection的子类,

但是使用instanceof有以下几点需要注意:

  • obj必须为引用类型,不能是基础类型
//基础类型
char c = '1';
System.out.println(c instanceof String);
//Inconvertible types; cannot cast 'char' to 'java.lang.String'
  • NULL

NULL是一个特殊的类型,可以是任意引用类型的特殊符号,可能是因为所有类都有空值,所以所有类型都有null。

//null
System.out.println(null instanceof Object);//false
  • obj为class类的实例对象
//class类的实例对象
String s = "123";
System.out.println(s instanceof String);//true
  • obj为class接口的实现类
//接口实现类,如ArrayList是AbstractList的子类,是List等的接口实现类
List<String> ls2 = new ArrayList<>();
System.out.println(ls2 instanceof List);//true
  • obj为class的直接或间接子类

看过第一个实例的话,我觉得你应该有自己的答案了。

//直接或间接子类
List<String> ls3 = new ArrayList<>();
System.out.println(ls3 instanceof AbstractCollection);//true
//在这里ls3是ArrayList类,ArrayList类是AbstractList类的子类,AbstractList类是AbstractCollection类的子类。

native

首先,native多用在方法上。

native方法就是Java调用非Java代码的接口,所以你见到的Java中含native关键字的方法大多都没有方法体,因为他们都是通过外接代码实现的。而使用native的主要原因是因为Java无法对操作系统底层进行操作,但是可以同过JNI(Java native interface)调用其他语言来实现底层访问。

构造方法

构造方法名与类名必须保持完全一致,包括类型及变量。

构造方法不含类型

class Test(int i){
    public Test(int i){
        //构造方法在new一个新的对象时会自动被调用
    }
}