Java基础知识学习:简单随手记录(1)

学习视频链接:https://www.bilibili.com/video/BV1fh411y7R8?p=1&vd_source=1635a55d1012e0ef6688b3652cefcdfe
(本文出现的程序和图片参考视频)

字符类型

  1. 字符常量是用单引号括起来的单个字符;
  2. 在Java中允许使用转义字符将后面的字符转变为特殊字符型常量;
  3. 在Java中,char的本质是一个整数,在输出时是Unicode码对应的字符;
  4. char类型可以进行运算,相当于一个整数

基本数据类型转换在这里插入图片描述
(注意:byte、short和char三者进行运行时会先转换为int型再运算)

自加运算

i = i++ 等价于“temp=i” + “i=i+1” + “i=temp”
i = ++i 等价于“i=i+1” + “temp=i” + “i=temp”

java数组初始化

数组属引用类型,数组型数据是对象(object),数组对象的长度初始化后长度就不可改变了,他们在堆内存中已经开辟了对应大小的内存空间。数组变量是一个引用类型的变量,数组变量在栈内存中指向存放在堆内存中的数组对象

//静态初试化:程序员决定数组元素的初始值,系统决定长度。
//静态初始化1
int[] arr = {1,2,3};
//上述式子相当于int[] a = new int[3];a[0] = 1;a[1] = 2;a[2] = 3;
//静态初始化2
String[] names = new String[]{"张三","李四","王五"}//动态初始化:程序员只决定数组元素的长度,系统分配元素初始值(通常为null,0等)。
//动态初始化1
int[] arr2 = new int[5]
//动态初始化2
double scores[] ; //声明数组, 这时 scores 是 null
scores = new double[5]; // 分配内存空间,可以存放数据

//下面列举几个判断正误的例子:
String strs[] = {'a','b'};//错误,相当于将字符赋给字符串
String[] strs = {"a","b"};//正确
String[] strs = new String{"a","b"};//错误,右边的String要加[]
String strs[] = new String[]{"a","b"}//正确
String[] strs = new String[2]{"a","b"}//错误,右边String的[]里不能加数字,要数字就不能加具体内容

(注意:将一个数组名arr指向另一个数组名arrNew时,原来数组名arr指向的数据空间就会没有变量引用,就会当成垃圾销毁)

//动态初始化1
int[][] arr = new int[2][3];
//动态初始化2
int arr[][];//声明二维数组
arr = new int[2][3];//再开空间

//一个有3个一维数组,但是每个一维数组还没有开数据空间
int[][] arr = new int[3][];
for(int i = 0; i < arr.length; i++) {//遍历 arr 每个一维数组
	//给每个一维数组开空间 new
	//如果没有给一维数组 new ,那么 arr[i]就是 null
	arr[i] = new int[i + 1];
	//遍历一维数组,并给一维数组的每个元素赋值
	for(int j = 0; j < arr[i].length; j++) {
	arr[i][j] = i + 1;//赋值
	}
}

//静态初始化1
int[][] arr = {{1,1,1}, {8,8,9}, {100}/*
1. 定义了一个二维数组 arr;
2. 2. arr 有三个元素(每个元素都是一维数组);
3. 3. 第一个一维数组有 3 个元素 , 第二个一维数组有 3 个元素, 第三个一维数组有 1 个元素
*/

Java的数据类型

Java分为两种数据类型:基本数据类型和引用数据类型
基本数据类型只有8种:

  • byte:字节型
  • short:短整型
  • int:整型
  • long:长整型
  • float:单精度
  • double:双精度
  • char:字符
  • boolean:布尔
    在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的;
    引用数据类型变量的具体内容都是存放在堆中的,在栈中存放的是具体内容所在内存的地址。

在方法中定义的非全局基本数据类型变量,调用方法时作为参数是值传递的
引用数据类型变量,调用方法时作为参数是按引用传递的

面向对象

Java总是采用按值调用的
在这里插入图片描述
方法调用流程:

  • 当程序执行到方法时,就会开辟一个独立的空间(栈空间);
  • 当方法执行完毕后,或者执行到return语句时就会返回;
  • 返回到调用方法的地方
  • 返回后继续执行方法后面的代码
  • 当main方法执行完毕,整个程序退出(栈)

跨类中的方法A 类调用 B 类方法:需要通过对象名调用
尝试在类的方法中,通过输入另一个类(地址)作为参数,修改另一个类的地址?(程序运行结果如下:)

函数重载注意事项:方法名必须相同,形参列表(类型或者个数或者顺序,至少有一个不同),返回类型随意
可变参数优化重载(一对多)

  • 可变参数的本质是数组
  • 可变参数可以和普通类型的参数放在形参列表中,但是可变参数必须是最后的
  • 一个形参列表只能出现一个可变参数
//1. int... 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)
//2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
//3. 遍历 nums 求和即可
public int sum(int... nums) {
	//System.out.println("接收的参数个数=" + nums.length);
	int res = 0;
	for(int i = 0; i < nums.length; i++) {
		res += nums[i];
	}
	return res;
}

之前创建对象时都是先将对象创建好(即在栈的引用对象?),然后再将对象里面的属性赋值,那能不能在创建时就指定对象的具体属性呢?这就要用到构造器(类似于C++的构造函数)
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化,有以下几个特点:

  • 构造器的修饰符可以默认, 也可以是 public protected private
  • 构造器没有返回值
  • 方法名 和类名字必须一样
  • 参数列表 和 成员方法一样的规则
  • 构造器的调用, 由系统完成
    注意:
  • 构造器是可以重载的;
  • 构造器是完成对象的初始化,并不是构建对象,具体就是在构建对象时系统自动调用该类的构造方法;
  • 一旦定义了自己的构造器,默认构造器就会被覆盖,就不可以在使用默认的无参构造器。
class Person {
	String name;
	int age;
	//构造器
	//1. 构造器没有返回值, 也不能写 void
	//2. 构造器的名称和类 Person 一样
	//3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样
	public Person(String pName, int pAge) {
		name = pName;
		age = p
	}
}

在这里插入图片描述

  • 大多new出来的对象一般都会放到堆里(具体结构,内部处理机制还没看)
  • 程序计数器用来存储下一条要执行的代码的内存位置(行号),而字节码执行引擎就会随着程序的执行不断修改程序计数器里面的内容
    【为什么需要程序计数器呢?Java是多线程的,要是被其他线程抢占CPU,这个线程就要挂起】
  • 当main这个主线程运行时,对于每一个线程都会被分配到部分内存(每个线程都会有自己独有的线程栈),局部变量放到栈(线程)里,
    栈帧:每一个方法都会在一大块线程栈分配自己的栈帧内存区,具体结构如下图所示,接下来简单说明每部分的作用:
    局部变量表和操作数栈可以分析下面那个代码来理解;
    (注意main的局部变量表存放的是对象的在堆上的地址,而对象的具体内容是放在堆上的)
    动态链接的作用就是将符号引用转为直接引用(代码上的方法函数名其实是符号引用,真正的函数存放在方法区里);
    方法出口就是在当时执行的线程的所有信息都会记录在方法出口,方法执行完后就可以通过方法出口回到main;
  • 方法区(元空间)是物理内存,或者说不是用虚拟内存的,主要存放一些常量、静态变量(如果是对象,当然存储的就是在堆的地址,具体的对象内容放在堆里)和类信息
  • 本地方法栈:执行到本地方法时,需要内存放数据就会在本地方法区中开辟内存空间
    在这里插入图片描述
int a = 1;
int b = 2;
int c = (a + b) * 10;
//1.将操作数1压到操作数栈里;2.为局部变量a在局部变量表中分配内存;3.操作数1出栈,并给到a的内存空间;
//4.将操作数2压到操作数栈里;5.为局部变量b在局部变量表中分配内存;6.操作数2出栈,并给到a的内存空间;
//7.从局部变量1中装载int类型值(将1放到操作数栈); 8.从局部变量2中装载int类型值(将2放到操作数栈);
//9.将操作数栈的栈顶两个元素弹出做加法运算得到3,将3压入操作数栈;
//10.将10压入操作数栈,然后弹出栈顶两个元素进行乘法,将30压入操作数栈;
//11.为局部变量c在局部变量表中分配内存,操作数30出栈,并给到c的内存空间;

包(在源文件下手动添加的)

  • 区分相同名字的类
  • 当类很多时管理类
  • 控制访问范围
    包的本质就是创建不同的文件夹/目录来保存类文件
    包的命名(名字前面加个package就行):
    在这里插入图片描述
//package 的作用是声明当前类所在的包,需要放在类(或者文件)的最上面,
// 一个类中最多只有一句 package
package com.hspedu.pkg;
//import 指令 位置放在 package 的下面,在类定义前面,可以有多句且没有顺序要求
import java.util.Scanner;
import java.util.Arrays;
//... //类定义
public class PkgDetail {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int[] arr = {0, -1, 1};
		Arrays.sort(args);
	}
}

访问修饰符

  • 公开级别:用 public 修饰,对外公开
  • 受保护级别:用 protected 修饰,同一个包内和其他包的子类中能被访问,这个权限处理的就是继承相关的概念
  • 默认级别(又称为包访问权限):没有修饰符号,向同一个包的类公开(同包、同类能用)
  • 私有级别:用 private 修饰,只有类本身可以访问,不对外公开(同类能用)
    (注意this和super也不能访问父类的private)

继承

  • 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
  • 子类必须调用父类的构造器, 完成父类的初始化
  • 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器(即子类中一定会存在一个Super()语句,不管有没有写出来,并且是默认指向父类的无参构造器的),如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不会通过。
  • 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
  • super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
//同一个包下的父类文件
class Base() {
	private int age;
	private String name;

	public Base() {};//无参构造函数
	public Base(int tmpAge) {//有参构造器1
		this.age = tmpAge;
		this.name = " ";
	}
	public Base(int tmpAge, String tmpName) {//有参构造器2
		this.age = tmpAge;
		this.name = tmpName;
	}
}

//同一个包下的子类文件
class ExtendBase extends Base() {
	public ExtendBase() {
		//super();//这条语句的逻辑是一定会执行的,并且不写或者写了无参都是指向父类的无参构造函数
		//如果父类没有无参构造函数,这里就一定要显式写出这条语句,并且参数是父类中某个构造函数的参数设置,表示调用的是那个构造函数
		super(12, "tom");//表示调用父类的有参构造器2
	};
	public ExtendBase (int tmpAge, String tmpName) {\
		this.age = tmpAge;
		this.name = tmpName;
	}
}
  • java 所有类都是 Object 类的子类, Object 是所有类的基类.
  • 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
  • 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制

  • super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
  • this只能在构造方法中调用其他构造方法
  • this要放在第一行
  • 一个构造方法中只能调用一个构造方法
  • super 代表父类的引用,用于访问父类的属性、方法、构造器
class A {
	A() {System.out.println("a");}
	A(String name) {System.out.println("a name");}
}
class B extends A {
	B() {
		this("abs");//表示无参构造函数的作用就是调用参数为string的B类的构造函数,(调自己的)
		System.out.println("b");
	}
	B(String name) {
		System.out.println("b name");
	}
}


//main中: B b_val = new B();的执行流程就是:
//1.上述新建对象是无参的,先看B的无参构造函数
//2.B是继承A的,并且没有用super指明,所以先调用A的无参构造函数,输出 a
//3.回到B的无参构造函数,this("abs")表示调用B的参数为String类型的构造函数,输出 b name
//4.接着执行B无参构造函数的最后一句语句, 输出 b

super的使用语法:

  • 访问父类的属性(非private):super.属性名
  • 访问父类的方法(非private):super.方法名(参数)
  • 访问父类的构造器:super(参数)
    (注意:)super的访问不只限于直接父类,如果祖父类和本类有同名成员,也可以使用super去访问祖父乐牛的成员,只不过是遵循就近原则,只能用最近的同名成员或方法

方法重写

方法覆盖是子类有一个方法和父类的某个方法的名称、返回类型、参数都一致,那么子类的这个方法就会覆盖父类的方法
在这里插入图片描述
子类方法不能缩小父类的方法的访问范围例子解释:即父类的一个方法是public时,子类对父类的这个方法重写时就只能设置为public,不能设置成protected或者private这些访问范围缩小的修饰

多态

方法的多态通过重写和重载体现
对象的多态(向上转型或者强制向下转型):

  • 一个对象的编译类型和运行类型可以不一致
  • 编译类型在定义对象是就确定
  • 运行类型是可以变换的
  • 编译类型看定义式 “=” 的左边,运行类型看 “=” 的右边
    (其实就是子类的对象可以赋值给父类的引用,只不过是只包含子类对象中父类的那些属性,如果想要将父类对象赋值给子类的对象引用,就要对父类对象进行强转)
    (还有一个注意点就是不同的子类A、B,假设它们是同一个父类的,但是不能将B对象强转后赋值给A的对象引用,强转应该只存在于父类与子类之间)
    引用对象的属性看编译类型(等号左边的类型),调用引用对象的具体方法时看运行类型(等号右边的类型)
class Base{
	int count = 10;
	public void display() {
		System.out.println(this.count);
	}
}

class Sub extends Base{
	int count = 20;
	public void display() {
		System.out.println(this.count);
	}
}

public static void main(String[] args) {
	Sub s = new Sub();
	System.out.println(s.count);//要输出的是s的属性值,看等号左边是Sub类型的,所以是Sub类型下的count = 20
	s.display();//调用s的方法,看等号右边是Sub是类型的,输出的是Sub类型下的count = 20
	Base b = s;//将地址也给到b,其实b和s就是一个地址,只不过b所代表的内容只是s的一部分(属于父类的那部分属性)
	System.out.println(b == s);//判断相同即判断地址 true
	System.out.println(b.count);要输出的是b的属性值,看等号左边是Base类型的,所以是Base类型下的count = 10
	b.display();//b的运行类型(等号右边)是Sub类型,就要调用Sub下的方法 20
}

再看一下下面的这个例子,理解动态绑定机制

  • 当调用对象方法的时候,该方法会和该对象的内存地址/运行地址绑定
  • 当调用对象属性的时候,哪里声明就取哪里的值(没有动态绑定机制)
public class myCode {
    public static void main(String[] args) {
		//a 的编译类型 A, 运行类型 B
        A a = new B();//第一步:向上转型,a的编译类型是A,运行类型是B
        System.out.println(a.sum());//第二步:调用a的sum方法,去B类找
        System.out.println(a.sum1());//第四步:调用a的sum1方法,去B类找
    }
}
class A {//父类
    public int i = 10;
    //动态绑定机制:
    public int sum() {//父类 sum()
        System.out.println("调用父类 sum()");
        return getI() + 10;//20 +
    }
    public int sum1() {//父类 sum1()
        System.out.println("调用父类 sum1()");
        return i + 10;//10 + 10
    }
    public int getI() {//父类 getI
        System.out.println("调用父类 getI()");
        return i;
    }
}
class B extends A {//子类
    public int i = 20;
    public int sum() {
    //第三步:进入B类的sum,此时i应该取值为20,就近取值,在哪个类就取对应类下的定义,最受输出20 + 20 = 40
        System.out.println("调用子类 sum()");
        return i + 20;
    }
    public int getI() {//子类 getI()
        System.out.println("调用子类 getI()");
        return i;
    }
     public int sum1() {
     //第五步:进入B类的sum1,此时i应该取值为20,就近取值,在哪个类就取对应类下的定义,最受输出20 + 10 = 30
         System.out.println("调用子类 sum1()");
         return i + 10;
     }
}
//对上述代码进行修改
public class myCode {
    public static void main(String[] args) {
		//a 的编译类型 A, 运行类型 B
        A a = new B();//第一步:向上转型,a的编译类型是A,运行类型是B
        System.out.println(a.sum());//第二步:调用a的sum方法,去B类找,发现B类没有,往父类A找
        System.out.println(a.sum1());//第六步:调用a的sum1方法,去B类找,发现B类没有,往父类A找
    }
}
class A {//父类
    public int i = 10;
    //动态绑定机制:
    public int sum() {//父类 sum()
    //第三步:进入父类的sum,发现要使用getI(),而a的运行类型是B,所以还是先往B类找
        System.out.println("调用父类 sum()");
    //第五步:获取通过B类的getI获取i值后,计算返回20 + 10 = 30
        return getI() + 10;//20 +
    }
    public int sum1() {//父类 sum1()
    //第七步:此时i取值为10,返回10 + 10 = 20
        System.out.println("调用父类 sum1()");
        return i + 10;//10 + 10
    }
    public int getI() {//父类 getI
        System.out.println("调用父类 getI()");
        return i;
    }
}
class B extends A {//子类
    public int i = 20;
    public int getI() {//子类 getI()
    //第四步:进入子类的getI,返回的i是20(就近取值)
        System.out.println("调用子类 getI()");
        return i;
    }
}

==和 equals 的对比

==是一个比较运算符

  1. 如果判断的是基本类型,则判断的是值是否相等
  2. 如果判断的是引用类型,则判断的是地址是否相等
  3. equals:属于超类Object的方法,如果不是String和Integer这两种重写过equals用于比较值的,就默认是比较地址

(这里是暂时不清楚用处的点,先按别人说的记一下)

hashCode 方法

  1. 提高具有哈希结构的容器的效率!
  2. 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
  3. 两个引用,如果指向的是不同对象,则哈希值是不一样的
  4. 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
    toString 方法
    1)默认返回:全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】
    子类往往重写 toString 方法,用于返回对象的属性信息
  5. 重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式
    3)当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调monster.toString()
    finalize 方法
  6. 当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作【演示】
  7. 什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来
    销毁该对象,在销毁该对象前,会先调用 finalize 方法。
  8. 垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制,测
    试:Car [name]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜以冀北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值