文章目录

  • 一句话目标
  • 标识符:
  • Java内存小知识:
  • 类与对象
    • 编程语言的几个发展阶段
      • 成员变量的赋值问题
    • 对象的创建与构造方法
      • 对象的内存模型
    • 类与程序的基本结构
    • 参数传值
    • 对象的组合
    • 实例成员与类成员
    • static关键字
      • static关键字的用途
      • static方法
    • 方法重载
    • this关键字
    • import语句
    • 访问权限
    • 对象数组
    • 小结
  • 子类与继承
    • 子类与父类
      • 人类
        • 属性:
        • 行为:
    • 子类的继承性
    • 子类与对象
    • 成员变量的隐藏和方法重写
      • 继承中成员变量的访问特点:
      • 方法重写
      • 总结:
    • 重写与重载
      • 方法的重写规则
      • 重载规则:
    • super关键字(super≈父类)
    • final关键字(不变)
    • Java 转型问题
      • 对象的上转型对象
      • 对象的下转型对象
    • 构造器
    • 继承与多态
    • abstract类和abstract方法
  • 接口与实现
    • 接口
    • 接口的特点
    • 接口定义与实现
      • 接口定义
      • 接口实现
    • abstract类与接口的比较
  • 字符串与ArrayList
      • 统计字符串
      • 数组拼接字符串
      • IDEA快捷键
  • 内部类
    • 内部类
    • 小结
  • 阶段项目
  • 常用API
  • 常见算法
  • 集合进阶
  • 异常类
  • 文件
  • IO流
  • 多线程与并发
  • Java最难的一部分:多线程与高并发

一句话目标


对于一些比较复杂或者第一眼看上去不太好理解的概念,我信奉的观念就是,用一句话把它解释清楚,而且是用很通俗的语言,当然了,如果你已经能够很好的理解了,还是建议用不是那么正式但又不是很通俗的语言解释。
所以我接下来我会用一句话这个标签来解释这些难懂的概念。

C/C++与Java编译运行过程对比:

数据溢出:
byte是一个字节的数据类型,所以它的表示范围是-128~127
当我们此范围之外的数赋值给byte类型变量时,会发生数据溢出,溢出的方式就是
比127大的数要接着顺时针转,假如是128,比127大1,那就是移动一个数变成-128假如是129,比127大2,那就是移动两个数变成-127,这样以此类推;
比-128小的数要接着逆时针,假如是-129,比-128小1,那就移动一个数变成127,假如是-130,比-128小2,那就是移动两个数变成126,其他数据类型也以此类推。

标识符:

大驼峰适用于类名,HelloWorld
小驼峰适用于方法名,变量名,helloWorld
四类八种数据类型:

数值拆分练习:
​ 获取任意一个数上每一位数。

个位:数字 % 10

十位:数字 / 10 % 10

百位:数字 / 100 % 10

千位:数字 / 1000 % 10

。。。以此类推。。。
取值范围从小到大的关系:byte short int long float double(小的跟大的运算,小的会提成为大的的类型)

Java内存小知识:

类与对象

编程语言的几个发展阶段


面向机器语言 ——》面向过程语言——》面向对象语言
(二进制、汇编)——》C语言 ——》Java语言

在面向过程编程中我们是以“方法”为主体的
而面向对象编程汇总我们是以“对象”为主体的
在面向对象语言的学习过程中,一个简单的理念就是,需要完成某种任务的时候,我们首先想到是谁去完成(对象);提到某个数据的时候,想到是谁的数据,这样也更符合我们日常生活中的描述。


类?类是干什么的呢?
一句话:类是用来描述和抽象具有相同属性和行为的一类事物的概念,就比如说人类,猫类,狗类,家禽类,这些类中的动物都有相同的属性和行为。
那么如何声明一个类呢?

class People{		//声明了一个人类
	//成员变量
	String Name;
	char sex;
	int Age;
	String Id;
	
	//成员方法
	void drink(){}
	void run(){}
	void walk(){}
	void eat(){}	
}

成员变量的赋值问题


Java规定不能在类中对成员变量进行赋值,但是你可以在类中对成员变量赋初始值,要想对成员变量赋值必须要在方法体内部进行
举个列子:

class People{
	int a = 10;		//声明的同时赋初始值,正确
	int b;			
	b = 27;			//非法
	void a(){
		b = b-19;	//在类中赋值,正确
	}
}

对象的创建与构造方法


类也可以看做是一种数据类型,也可以用来声明变量,而用类声明的变量被称为对象。
既然类有了,总得有对象来体现这个类的属性和行为吧,那我们就可以创建对象来体现了
创建对象要用关键字new

People zs;						//声明对象张三
People zs = new People();		//创建对象张三

构造方法的名称必须与它坐在的类名称完全一致,而且没有类型,可以有参数,参数一般就是对对象的成员变量进行赋值。
所以这样看起来构造方法在创建对象以及对对象进行初始化起到了至关重要的作用。
构造方法可以有多个,但是其参数一定不同(参数类型、参数个数)。
构造方法不就是把对象的值赋值给实例变量。

class People{		//创建了一个人类
	//成员变量
	//注意:当没有指定成员变量的值时,其都有默认值,String为null,int为0,float为0.0,boolean为flase
	String Name;
	char sex;
	int Age;
	String Id;
	
	//成员方法
	void drink(){}
	void run(){}
	void walk(){}
	void eat(){}	

	People(String N){	//构造函数
		Name = N;
	}
	People(String s){	//构造函数
		sex = s;
	}
	People(){			//构造函数
	}
	
}

对象的内存模型


声明对象和创建对象是有区别的:

People zs;						//声明对象张三
//声明对象,内存为空

People zs = new People();		//创建对象张三,可以看做是new一个构造方法
/*创建对象,分配内存,先为成员变量
	String Name;
	boolean sex;
	int Age;
	String Id;
分配内存,如果未赋值再赋给默认值,最后new运算符会计算出一个引用值(地址值),即表达式new People()是一个值。


这个内存模型啊,告诉我们一件事,声明和创建区别很大

类与程序的基本结构


一个应用程序可以有多个源文件,一个源文件可以有多个类,但是一定要有一个主类

参数传值


说一下引用型参数
当参数是数组、对象、接口的时候,称之为引用型参数,传的是引用(地址)而不是实体
举例:

class Battery{			//电池类
	int e;		//电量
	Battery(int a){
		e = a;
	}
}

class Radio{
	void openRadio(Battery battery){
	 	battery.e = battery.e - 10;		//消耗了电量
	 }
}

public class Example{
 	public static void main(String args[]){
 	 	Battery b = new Battery(100);		//创建一个电池对象,电量初始化为100
 	 	Radio r = new Radio();			//创建一个收音机对象
 	 	r.openRadio(b);					//参数为电池对象b,此时就相当于把b的值赋给Battery Battery
 	 									//而b = new Battery(),所以在方法openRadio中就相当于Battery battery = new Battery()
 	 									//由上可知道对象b和battery他们两个是一模一样的,他们具有相同的引用,故具有完全相同的变量
 	 									//并且battery就相当于b的复印件,复印件的改变不会影响原件
 	 									
 	 	
 	 }
 }

对象的组合


一句话:将其他类的对象作为自己的成员变量
对象组合的本质就是一个类的数据成员变量存的不是基本的数据类型,而是一个对象的地址
举例:

class Circle{

	double rad;	//半径
	double area;	//面积
	
	double getArea(){
	 	area = 3.14*r*r;
	 	return area;
	 }
	Circle(double r){
	 	rad = r;
	 }
}

class Circular{		//圆锥类

 	Circle bottom;
 	double h;
 	
 	Circular(double height){
 	 	h = height;
 	 }
 	 void setBottom(Circle c){
 	  	bottom = c;
 	  }
 	double	getVolme(){
 	 	if(bottom == null)
 	 		return -1;
 	 	else
 	 		return bottom.getArea()*h/3.0;
 	 }
 }

public class Example0{
 	public static void main(String args[]){
 	 	Circle c = new Circle(100);
 	 	Circular cu = new Circular(20);
 	 	cu.setBottom(c);
 	 	int V = cu.getVolme();
 	 	System.out.println("圆锥的体积为:"+V);
 	 }
 }

实例成员与类成员


首先类成员中的类变量被所有对象共享,也就是所有的对象的类变量是相同的一处内存空间,并且通过类名访问类变量和通过对象访问类变量都可以改变类变量的值。
加载类的字节码文件的时候,类变量已经分配了内存,而成员变量没有;当该类创建对象时,才会给实例对象分配内存

实例成员就是正常的变量和方法
类成员就是静态的变量和方法(前面加了static修饰的)

类变量也叫做static变量、静态变量、全局变量,我叫它共享变量,分配给这些对象的类变量占有相同的一处内存,改变其中一个对象的这个类变量,其他对象的这个类变量也会跟着变;static方法可以重写,重写就是子类可以重写父类已有的方法。

static关键字

在类中,用static声明的成员变量为静态成员变量,也成为类变量。类变量的生命周期和类相同,在整个应用程序执行期间都有效。

这里要强调一下:

  • static修饰的成员变量和方法,从属于类
  • 普通变量和方法从属于对象
  • 静态方法不能调用非静态成员,编译会报错

static关键字的用途

一句话描述就是:方便在没有创建对象的情况下进行调用(方法/变量)。

显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。

static可以用来修饰类的成员方法、类的成员变量,另外也可以编写static代码块来优化程序性能

static方法

static方法也成为静态方法,由于静态方法不依赖于任何对象就可以直接访问,因此对于静态方法来说,是没有this的,因为不依附于任何对象,既然都没有对象,就谈不上this了,并且由于此特性,在静态方法中不能访问类的非静态成员变量和非静态方法,因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。

虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法和静态成员变量。

代码示例

从上面代码里看出:

  • 静态方法test2()中调用非静态成员变量address,编译失败。这是因为,在编译期并没有对象生成,address变量根本就不存在。
  • 静态方法test2()中调用非静态方法test1(),编译失败。这是因为,编译器无法预知在非静态成员方法test1()中是否访问了非静态成员变量,所以也禁止在静态方法中调用非静态成员方法
  • 非静态成员方法test1()访问静态成员方法test2()/变量name是没有限制的

所以,如果想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。最常见的静态方法就是main方法,这就是为什么main方法是静态方法就一目了然了,因为程序在执行main方法的时候没有创建任何对象,只有通过类名来访问。

举例:

class A{
 	static int a;		//类变量
 	float b;			//成员变量
 	
 	set_a(int A){
 	 	a = A;
 	 }
 	static void c(){	//类方法
 	 	System.out.println("Hello World!");
 	 }
 	int d(int x,int y){	//成员方法
 	 	int c;
 	 	return c = x*y;
 	 }
 }
public class B{
 	public static void main(String args[]){
 	 	A.a = 10; 		//通过类名给类变量a赋值,10
 	 	A a1 = new A();
 	 	System.out.println(A.a);	//输出10
 	 	a1.set_a(20);	//通过对象调用给类变量赋值,20
 	 	System.out.println(A.a);	//输出20
 	 }
 	
 }

实例方法和类方法的区别:

  • 有对象才有实例方法的入口地址;将类的字节码文件加载至jvm内存时,不会为实例方法分配内存;
  • 但是会给类方法分配入口地址。多个对象的实例方法的入口是相同的,也就是实例方法的入口地址被共享;
  • 只有在所有的对象被回收时,入口地址才会被取消;
  • 而类方法的入口地址在程序退出的时候才被取消。
  • 可以把类变量放在实例方法中(把有static修饰的变量放在没有static修饰的方法中),也就是实例方法可以操作类变量;
  • 但是类方法不可以操作实例变量;原因是:再类创建对象之前,实例成员变量还没有分配内存空间。
  1. static方法中只能有static变量

方法重载


顾名思义是对方法进行重新加载。
那么对哪些方法会重新加载呢?
同一个方法名,但是它们参数的类型不同,它们参数的个数不同。
这种例子很多啊,比如说

f(int a,int b);
f();
f(double a,double b);
//这三个方法如果放在同一个类中,那么就叫做对f进行方法重载

this关键字


this是当前对象的引用,就是说当前用构造函数建的对象是谁,这个this就代表谁,它是一个引用。
this可以出现在:

  • 实例方法
  • 构造方法
  • 但是不可以出现在类方法中

不就是你的方法的参数名跟你的成员变量名一样的时候,你要是执行语句:

成员变量名=参数名

就必须写成:

this.成员变量名=参数名

注:

  1. 通常情况下:可以省略成员变量名字前的“this.”,以及static变量前的“类名.”;
  2. this不能出现在类方法中, 因为类方法可以通过类名直接调用,这时可能还没有对象诞生。


一句话说明包的目的:区分不同文件中相同类

import语句


一句话:相当于C语言中的#include,就是导入在源程序中要用的各种库或者自己写好的接口。
也就是在同一目录下的Java文件是互通的,要通过import与其他包文件进行通讯。
在java中使用类库就是创建相应的对象(所以说Java是面向对象的语言)。
如果使用import导入了包中的所有类,那么会增加编译时间,但是不会影响程序的性能,因为jvm只加载自己程序要用的(可Java本来就慢啊= =)。

访问权限



类方法总是可以操作类中的类变量,与访问控制符没有关系。

  1. public
    被public标记的变量和方法在任何地方的对象都可以访问(类内部、本包、子类、外部包)
  2. protected
    被propected标记的变量和方法仅在本包内可以访问。
  3. 友好的
    同上
  4. private
    只有类内部使用

注:

  • 只能用public来修饰类
  • 权限由高到低:public——>protected——>友好的——>private
    protected和友好型的区别:
    当子类和父类不在同一个包中时,父类中的private和友好访问权限的成员变量和方法不会被子类继承;在同一个包中时,子类会将父类的变量和方法除private之外全部继承。
    如果子类和父类不在同一个包中,子类不继承父类的友好成员变量和方法

对象数组


如果要一次定义很多对象,建议使用对象数组而不是定义多个对象。

小结

子类与继承

子类与父类


既然我们的代码要描述我们的现实生活那么应该怎么做呢?我们显示生活中父亲生儿子这种事,那可是几乎都存在的啊,不管是人类,还是其他生物,几乎都存在,那就我们也让我们的代码可以继承,让我们原本定义的类,可以让他派生自己的子类,也就是让子类来继承父类的一些属性以及行为。(儿子只能有一个爹,爹却可以生很多儿子)
这里问一个面试常问的问题:

为什么 Java 语⾔不⽀持多重继承?

  • 为了程序的结构能够更加清晰从⽽便于维护。假设 Java 语⾔⽀持多重继承,类 C 继承⾃类 A 和类 B,如果类 A 和 B 都有⾃定义的成员⽅法 f() ,那么当代码中调⽤类 C 的 f() 会产⽣⼆义性。
  • Java 语⾔通过实现多个接⼝间接⽀持多重继承,接⼝由于只包含⽅法定义,不能有⽅法的实现,类 C 继承接⼝ A 与接⼝ B 时即使它们都有⽅法 f() ,也不能直接调⽤⽅法,需实现具体的 f() ⽅法才能调⽤,不会产⽣⼆义性。
  • 多重继承会使类型转换、构造⽅法的调⽤顺序变得复杂,会影响到性能。

好了,言归正传,继承的关键字是什么呢?

extends

最简单的例子啊,人类是个父类,学生类是个子类,那可以这么说:学生类继承了人类

人类

属性:

  • 姓名
  • 性别
  • 年龄
  • 身份证号

行为:

  • 喝水
  • 跑步
  • 散步
  • 吃饭
class People{
	String Name;
	char sex;
	int Age;
	String Id;

	void drink(){}
	void run(){}
	void walk(){}
	void eat(){}
}

那么学生类要继承人类的话,就要把人类所有的属性都原封不动的继承过来,然后如果学生类有需求的话再在学生类中填入自己需要的属性或行为

class Student{
	String strID;		//新添的属性学号
	String Subject;		//课程
	
	void exercise(){}	//广播体操
	void finalExam(){}	//期末考试
}

上面这些属性和行为是新添加的,都是学生所特有的

子类的继承性


在同一包中:
在同一个包中子类继承父类时,不会继承private标记的成员变量和方法,但是会继承友好的成员变量和方法。(父亲的隐私可不兴看啊)
不在同一包中:
不在同一个包中的子类继承父类时,private和友好的成员变量和方法都不会继承。

protected进一步说明的举例:
我们有一个类D,还有一个类C

  • D类中自己声明的protected成员变量和方法,只要D跟C在同一包中,则在C类中创建的D对象可以访问这些protected成员变量和方法;
  • D类继承自己父类的那些protected成员变量和方法,需要追溯到这些protected成员变量和方法所在的祖先类跟C类是否在同一包中,若在,则在C类中创建的D对象可以访问这些protected成员变量和方法,反之,则不可以。

子类与对象


创建子类对象时,jvm不仅会为子类的成员变量分配内存还会为父类的成员变量分配内存。
但是我们知道子类继承父类时,并不会全部东西都继承过来,有时候会因为不在同一个包中,protected、友好型和private都不能继承,那么父类这个时候为什么还要为这些不能继承的成员变量分配内存呢?
答案是:子类会用那些从父类继承过来的方法来操作这部分未继承的变量。

成员变量的隐藏和方法重写


在子类继承父类的过程中,因为要继承两部分:变量和方法

  • 自己再写一遍父类有的变量就叫做隐藏
  • 自己再写一遍父类有的方法就叫重写

继承中成员变量的访问特点:

在父子 类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

  1. 直接通过子类对象访问成员变量:也就是对象.成员变量,规则是:创建对象时,等号左边是谁,就优先使用谁,没有则向上找。
  2. 间接通过成员方法访问成员变量:该方法属于谁,就优先用谁,没有则向上找。
public class Fu {

    int numFu = 10;

    int num = 100;

    public void methodFu() {
        // 使用的是本类当中的,不会向下找子类的
        System.out.println(num);
    }

}

public class Zi extends Fu {

    int numZi = 20;

    int num = 200;

    public void methodZi() {
        // 因为本类当中有num,所以这里用的是本类的num
        System.out.println(num);
    }

}


/*
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

直接通过子类对象访问成员变量:
    等号左边是谁,就优先用谁,没有则向上找。
间接通过成员方法访问成员变量:
    该方法属于谁,就优先用谁,没有则向上找。
 */
public class Demo01ExtendsField {

    public static void main(String[] args) {
        Fu fu = new Fu(); // 创建父类对象
        System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容
        System.out.println("===========");

        Zi zi = new Zi();

        System.out.println(zi.numFu); // 10
        System.out.println(zi.numZi); // 20
        System.out.println("===========");

        // 等号左边是谁,就优先用谁
        System.out.println(zi.num); // 优先子类,200
//        System.out.println(zi.abc); // 到处都没有,编译报错!
        System.out.println("===========");

        // 这个方法是子类的,优先用子类的,没有再向上找
        zi.methodZi(); // 200
        // 这个方法是在父类当中定义的,
        zi.methodFu(); // 100
    }

}

方法重写

在父子类的继承关系当中,创建子类对象,访问成员方法的规则:

创建的对象是谁,就优先用谁,如果没有则向上找。

注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。

如何理解这个隐藏呢?

就是我子类不是会继承你父类的部分变量吗?
比如父类有一个变量:
public a;
如果我在子类中也声明了public a ;
那么子类从父类继承的a就会被隐藏。
说白了就是你子类要是有的话就用自己的,可以不用父类的了(这个“不用”就可以理解隐藏),要是没有,那就用父类的。
)

总结:

  • 方法:看等号右边(创建的是谁),就优先调用谁
  • 变量:看等号左边是谁,就优先调用谁;看方法属于谁,就优先调用谁

重写与重载



重写就是我子类重新写父类的一些同名方法,返回值和形参都不能改变。即外壳不变,核心重写

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

super关键字(super≈父类)


super关键字的用法有三种:
1.在子类的成员方法中,访问父类的成员变量。
2.在子类的成员方法中,访问父类的成员方法。
3.在子类的构造方法中,访问父类的构造方法。

一句话说明作用:
当你在子类的方法中需要访问父类的变量或者方法时,就需要使用super关键字来访问。
很显然super这个关键字也是跟继承、父类和子类有关的概念。

public class Fu {
    int num = 10;
    public void method(){
        System.out.println();
    }
}

public class Zi extends Fu {
    int num = 20;
    public  Zi(){
        super();
    }
    public void  methodZi(){
        System.out.println(super.num); //父类中的num
    }
    public void method(){
        super.method(); //访问父类中的method
        System.out.println("子类方法");
    }
}

注意:super 语句必须是子类构造方法的第一条语句。不能在子类中使用父类构造方法名来调用父类构造方法。 父类的构造方法不被子类继承。调用父类的构造方法的唯一途径是使用 super 关键字,如果子类中没显式调用,则编译器自动将 super(); 作为子类构造方法的第一条语句。静态方法中不能使用 super 关键字。

调用父类的方法语法:

super.方法名(参数列表);

如果是继承的方法,是没有必要使用 super 来调用,直接即可调用。但如果子类覆盖或重写了父类的方法,则只有使用 super 才能在子类中调用父类中的被重写的方法。

final关键字(不变)


  • 修饰类:不可以有子类
  • 修饰方法:可以被继承,但继承后不能被重写。(老老实实继承)
  • 修饰变量:变为常量

final 修饰类中的属性或者变量

无论属性是基本类型还是引用类型,final 所起的作用都是变量里面存放的"值"不能变。

这个值,对于基本类型来说,变量里面放的就是实实在在的值,如 1,“abc” 等。

而引用类型变量里面放的是个地址,所以用 final 修饰引用类型变量指的是它里面的地址不能变,并不是说这个地址所指向的对象或数组的内容不可以变,这个一定要注意。

例如:类中有一个属性是 final Person p=new Person(“name”); 那么你不能对 p 进行重新赋值,但是可以改变 p 里面属性的值 p.setName(‘newName’);

final 修饰属性,声明变量时可以不赋值,而且一旦赋值就不能被修改了。对 final 属性可以在三个地方赋值:声明时、初始化块中、构造方法中,总之一定要赋值。

Java 转型问题


Java 转型问题其实并不复杂,只要记住一句话:父类引用指向子类对象。
什么叫父类引用指向子类对象,且听我慢慢道来。

从 2 个名词开始说起:向上转型(upcasting)向下转型(downcasting)

举个例子:有2个类,Father 是父类,Son 类继承自 Father。

第 1 个例子:

Father f1 = new Son();   // 这就叫 upcasting (向上转型)
// 现在 f1 引用指向一个Son对象

Son s1 = (Son)f1;   // 这就叫 downcasting (向下转型)
// 现在f1 还是指向 Son对象

第 2 个例子:

Father f2 = new Father();
Son s2 = (Son)f2;       // 出错,子类引用不能指向父类对象

你或许会问,第1个例子中:Son s1 = (Son)f1; 问为什么是正确的呢。

很简单因为 f1 指向一个子类对象,Father f1 = new Son(); 子类 s1 引用当然可以指向子类对象了。

而 f2 被传给了一个 Father 对象,Father f2 = new Father(); 子类 s2 引用不能指向父类对象。

总结:

1、父类引用指向子类对象,而子类引用不能指向父类对象。

2、把子类对象直接赋给父类引用叫upcasting向上转型,向上转型不用强制转换吗,如:

Father f1 = new Son();

3、把指向子类对象的父类引用赋给子类引用叫向下转型(downcasting),要强制转换,如:

f1 就是一个指向子类对象的父类引用。把f1赋给子类引用 s1 即 Son s1 = (Son)f1;

其中 f1 前面的(Son)必须加上,进行强制转换。

对象的上转型对象


通俗地讲即是将子类对象转为父类对象。

上转型对象的特点:

  • 上转型对象不能操作子类新增的变量和方法
  • 上转型对象可以访问子类继承或者隐藏的成员变量,也可以调用子类继承的方法或者重写的方法。
  • 如果子类重写了父类的某个实例方法后,当上转型对象调用这个方法时,一定调用的是子类重写的,若子类重写了父类的static方法,则调用时只能调用父类的。
  • 不可以将父类创建的对象的应用赋值给子类声明的对象(不能说:人是中国人)
    举例:
public class Animal {
  
  public void eat(){
    System.out.println("animal eatting...");
  }
}
class Bird extends Animal{
  
  public void eat(){
    System.out.println("bird eatting...");
  }
  
  public void fly(){
    
    System.out.println("bird flying...");
  }
}
class Main{
  
  public static void main(String[] args) {
    
    Animal b=new Bird(); //向上转型
    b.eat(); 
    //! error: b.fly(); b虽指向子类对象,但此时丢失fly()方法
    dosleep(new Male());
    dosleep(new Female());
  }

注意这里的向上转型:

Animal b=new Bird(); //向上转型
b.eat();

此处将调用子类的 eat() 方法。原因:b 实际指向的是 Bird 子类,故调用时会调用子类本身的方法。

需要注意的是:
向上转型时 b 会遗失除与父类对象共有的其他方法。如本例中的 fly 方法不再为 b 所有。
因此输出结果:bird eatting...

对象的下转型对象


与向上转型相反,即是把父类对象转为子类对象。

public class Girl {
  public void smile(){
    System.out.println("girl smile()...");
  }
}
class MMGirl extends Girl{
  
  @Override
  public void smile() {
    
    System.out.println("MMirl smile sounds sweet...");
  }
  public void c(){
    System.out.println("MMirl c()...");
  }
}
class Main{
  
  public static void main(String[] args) {
    
    Girl g1=new MMGirl(); //向上转型
    g1.smile();
    
    MMGirl mmg=(MMGirl)g1; //向下转型,编译和运行皆不会出错
    mmg.smile();
    mmg.c();
    
    
    Girl g2=new Girl();
//    MMGirl mmg1=(MMGirl)g2; //不安全的向下转型,编译无错但会运行会出错
//    mmg1.smile();
//    mmg1.c();
/*output:
* CGirl smile sounds sweet...
* CGirl smile sounds sweet...
* CGirl c()...
* Exception in thread "main" java.lang.ClassCastException: com.wensefu.other1.Girl
* at com.wensefu.other1.Main.main(Girl.java:36)
*/
    if(g2 instanceof MMGirl){
      MMGirl mmg1=(MMGirl)g2; 
      mmg1.smile();
      mmg1.c();
    }
    
  }
}
Girl g1=new MMGirl(); //向上转型
g1.smile();
MMGirl mmg=(MMGirl)g1; //向下转型,编译和运行皆不会出错

这里的向下转型是安全的。因为 g1 指向的是子类对象。

Girl g2=new Girl();
MMGirl mmg1=(MMGirl)g2; //不安全的向下转型,编译无错但会运行会出错

运行出错:

Exception in thread "main" java.lang.ClassCastException: com.wensefu.other1.Girl
    at com.wensefu.other1.Main.main(Girl.java:36)

构造器


子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
子类构造方法总是先调用父类的构造方法,你没写super(有参数),则默认你写了super(无参数)。
举例:

class SuperClass {
  private int n;
  SuperClass(){
    System.out.println("SuperClass()");
  }
  SuperClass(int n) {
    System.out.println("SuperClass(int n)");
    this.n = n;
  }
}
// SubClass 类继承
class SubClass extends SuperClass{
  private int n;
  
  SubClass(){ // 自动调用父类的无参数构造器
    System.out.println("SubClass");
  }  
  
  public SubClass(int n){ 
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass(int n):"+n);
    this.n = n;
  }
}
// SubClass2 类继承
class SubClass2 extends SuperClass{
  private int n;
  
  SubClass2(){
    super(300);  // 调用父类中带有参数的构造器
    System.out.println("SubClass2");
  }  
  
  public SubClass2(int n){ // 自动调用父类的无参数构造器
    System.out.println("SubClass2(int n):"+n);
    this.n = n;
  }
}
public class TestSuperSub{
  public static void main (String args[]){
    System.out.println("------SubClass 类继承------");
    SubClass sc1 = new SubClass();
    SubClass sc2 = new SubClass(100); 
    System.out.println("------SubClass2 类继承------");
    SubClass2 sc3 = new SubClass2();
    SubClass2 sc4 = new SubClass2(200); 
  }
}

输出结果为:

------SubClass 类继承------
SuperClass()
SubClass
SuperClass(int n)
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)
SubClass2
SuperClass()
SubClass2(int n):200

好像有点感觉了,我们将代码做一点改动,再次感受一下:
subClass()构造方法中添一句

super(200);

然后将父类有参数构造方法改为:

    SuperClass(int n) {
        this.n = n;
        System.out.println("SuperClass(int n)"+this.n);

再次运行,得到结果:

------SubClass 类继承------
SuperClass(int n)200
SubClass
SuperClass(int n)300
SubClass(int n):100
------SubClass2 类继承------
SuperClass(int n)300
SubClass2
SuperClass()
SubClass2(int n):200

继承与多态


首先多态是个跟继承有关的概念,
什么叫多态?
多态就是在描述生活
举个例子
多态就是我有一个动物类,然后动物类派生了两个子类,猫类和狗类
我当初在动物类中写了一个方法:发出叫声
很显然猫跟狗的叫声不一样,一个喵喵,一个汪汪,那么我当初在动物类中写的这个发出叫声的方法现在如果这两个子类要用的话,是不是就得在猫类和狗类里面重写了,重写完之后,这整个过程就叫做多态
多态就是 同一个方法的不同实现。

abstract类和abstract方法


在java中我们用abstract关键字来表达抽象。举个例子:
我们说车子都可以跑(run)。但有几个轮子,怎么跑,对于不同的车有不同的结果。自行车需要人踩着跑,汽车发动机推动跑等等,那么我们可以车表达为抽象类。

/**
 * 车子类
 */
public abstract class Car {
	
	public abstract void run();
}
/**
 * 自行车
 */
class Bicycle extends Car{

	@Override
	public void run() {
		System.out.println("人踩着跑。。。");
	}
	
}
/***
 * 汽车
 */
class Automobile extends Car{

	@Override
	public void run() {
		System.out.println("发动机驱动跑。。。");
	}
	
}

我的理解是抽象更像是一种概念,只要是抽象的就不需要具象,抽象类不需要实例化,抽象方法不需要在本类中实现。
抽象方法是一个概念,不用实现!!只要你在一个方法前冠以abstract,那这个方法就变成了一个概念,你不需要去实现一个概念。抽象方法跟接口中的方法是类似的,都不需要实现,所以我们没办法直接调用抽象方法

abstract类中声明的abstract方法要在子类中实现,如果子类未实现必须要将子类也声明为abstract:

abstract类:

1、用abstract关键字来表达的类,其表达形式为:(publicabstract class 类名{}

2、抽象类不能被实例化,也就是说我们没法直接new 一个抽象类。抽象类本身就代表了一个类型,无法
确定为一个具体的对象,所以不能实例化就合乎情理了,只能有它的继承类实例化。

3、抽象类虽然不能被实例化,但有自己的构造方法(这个后面再讨论)

4、抽象类与接口(interface)有很大的不同之处,接口中不能有实例方法去实现业务逻辑,而抽象类
中可以有实例方法,并实现业务逻辑,比如我们可以在抽象类中创建和销毁一个线程池。

5、抽象类不能使用final关键字修饰,因为final修饰的类是无法被继承,而对于抽象类来说就是
需要通过继承去实现抽象方法,这又会产生矛盾。(后面将写一篇关于finally的文章详细讨论)

abstract方法:

1、从上面的例子中我们可以看到抽象方法跟普通方法是有区别的,它没有自己的主体(没有{}包起来的
业务逻辑),跟接口中的方法有点类似。所以我们没法直接调用抽象方法

2、抽象方法不能用private修饰,因为抽象方法必须被子类实现(覆写),而private权限对于子类来
说是不能访问的,所以就会产生矛盾

3、抽象方法也不能用static修饰,试想一下,如果用static修饰了,那么我们可以直接通过类名调
用,而抽象方法压根就没有主体,没有任何业务逻辑,这样就毫无意义了。

总结:

/*
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。

如何使用抽象类和抽象方法:
1. 不能直接创建new抽象类对象。
2. 必须用一个子类来继承抽象父类。
3. 子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。
4. 创建子类对象进行使用。
 */

接口与实现

接口


官方解释:Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。


个人理解:接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)

接口的特点


就像一个类一样,一个接口也能够拥有方法和属性,但是在接口中声明的方法默认是抽象的。(即只有方法标识符,而没有方法体)。

如果一个类实现了一个接口中要求的所有的方法,然而没有提供方法体而仅仅只有方法标识,那么这个类一定是一个抽象类。(必须记住:抽象方法只能存在于抽象类或者接口中,但抽象类中却能存在非抽象方法,即有方法体的方法。接口是百分之百的抽象类)

//定义子类,继承父类,实现接口
public class Zi extends Fu implements MyInterface {

接口定义与实现


接口定义

定义接口用关键词interface

/*
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);

注意事项:
1. 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
2. 这两个关键字修饰符,可以选择性地省略。(今天刚学,所以不推荐。)
3. 方法的三要素,可以随意定义。
 */
public interface MyInterfaceAbstract {

    // 这是一个抽象方法
    public abstract void methodAbs1();

    // 这也是抽象方法
    abstract void methodAbs2();

    // 这也是抽象方法
    public void methodAbs3();

    // 这也是抽象方法
    void methodAbs4();

}

/*
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
格式:
public static final 数据类型 常量名称 = 数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变。

注意事项:
1. 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
2. 接口当中的常量,必须进行赋值;不能不赋值。
3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
 */
public interface MyInterfaceConst {

    // 这其实就是一个常量,一旦赋值,不可以修改
    public static final int NUM_OF_MY_CLASS = 12;

}

接口实现

public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
    @Override
    public void methodAbs1() {
        System.out.println("这是第一个方法!");
    }

    @Override
    public void methodAbs2() {
        System.out.println("这是第二个方法!");
    }

    @Override
    public void methodAbs3() {
        System.out.println("这是第三个方法!");
    }

    @Override
    public void methodAbs4() {
        System.out.println("这是第四个方法!");
    }
}

abstract类与接口的比较

在Java语言中,abstract class和interface是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。

  1. 1.相同点
    A. 两者都是抽象类,都不能实例化。
    B. interface实现类及abstrct class的子类都必须要实现已经声明的抽象方法。

  2. 不同点
    A. interface需要实现,要用implements,而abstract class需要继承,要用extends。
    B. 一个类可以实现多个interface,但一个类只能继承一个abstract class。
    C. interface强调特定功能的实现,而abstract class强调所属关系。
    D. 尽管interface实现类及abstrct class的子类都必须要实现相应的抽象方法,但实现的形式不同。interface中的每一个方法都是抽象方法,都只是声明的 (declaration, 没有方法体),实现类必须要实现。而abstract class的子类可以有选择地实现。
    这个选择有两点含义:
    一是Abastract class中并非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子类必须实现。那些没有abstract的方法,在Abstrct class中必须定义方法体。
    二是abstract class的子类在继承它时,对非抽象方法既可以直接继承,也可以覆盖;而对抽象方法,可以选择实现,也可以通过再次声明其方法为抽象的方式,无需实现,留给其子类来实现,但此类必须也声明为抽象类。既是抽象类,当然也不能实例化。
    E. abstract class是interface与Class的中介。
    interface是完全抽象的,只能声明方法,而且只能声明pulic的方法,不能声明private及protected的方法,不能定义方法体,也 不能声明实例变量。然而,interface却可以声明常量变量,并且在JDK中不难找出这种例子。但将常量变量放在interface中违背了其作为接 口的作用而存在的宗旨,也混淆了interface与类的不同价值。如果的确需要,可以将其放在相应的abstract class或Class中。
    abstract class在interface及Class中起到了承上启下的作用。一方面,abstract class是抽象的,可以声明抽象方法,以规范子类必须实现的功能;另一方面,它又可以定义缺省的方法体,供子类直接使用或覆盖。另外,它还可以定义自己 的实例变量,以供子类通过继承来使用。

  3. interface的应用场合
    A. 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
    B. 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
    C. 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
    D. 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。

  4. abstract class的应用场合
    一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
    A. 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
    B. 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
    C. 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能。

字符串与ArrayList

用new创建会重新开辟内存空间,而用赋值方式创建会只要字符串的顺序大小写完全相同则,只创建一个String对象。

==:两个字符串地址是否相等
equals:两个字符串内容是否相等

统计字符串

需求:​键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)

需要注意的是调用一个charAt(i);方法,根据索引返回字符串,字符串是有索引的哦
还有一个小点需要注意,数组的length是一个属性无须加括号,而字符串的length是一个方法需要加。charAt() 方法用于返回指定索引处的字符。
核心:判断一个字符属于大写字母?小写字母?数字?
代码实现:

import java.util.Scanner;

public class StatisticsCount {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入一个字符串");
            String s1 = sc.next();

            int smallCount = 0;
            int bigCount = 0;
            int numberCount = 0;


            for (int j = 0; j < s1.length(); j++) {
                char c = s1.charAt(j);
                if(c>='a'&&c<='z')
                    smallCount++;
                else if(c>='A'&&c<='Z')
                    bigCount++;
                else if (c>='0'&&c<='9')
                    numberCount++;
            }
            System.out.println("大写字符的个数:"+bigCount);
            System.out.println("小写字符的个数:"+smallCount);
            System.out.println("数字字符的个数:"+numberCount);
            System.out.println("==========================");

        }

    }
}

数组拼接字符串

注意:定义方法,我要做什么,我需要什么,我需要返回什么…,然后考虑是否需要校验传过来的参数内容,是否为空?是否不符合格式…等等,并对其进行解决

public class ArrToString {
    public static void main(String[] args) {
        int[] arr = {1,2,3};
        String s1 = arrToString(arr);
        System.out.println(s1);
    }


    public static String arrToString(int[] arr){
    //判断传来的字符串是否为空
        if(arr == null){
            return "";
        }

        if(arr.length ==0){
            return"[]";
        }

        String result = "[";

        for (int i = 0; i <arr.length; i++) {
            if(i==arr.length-1){        //若是最后一个元素则不加,
                result = result + arr[i];
            }
            else
            result = result + arr[i]+", ";
        }
        return result +"]";
    }


}

训练中的问题:

  • 方法中忘记添加边界条件
  • 部分变量犹豫放在循环内还是循环外
  • Scanner使用next()获取输入内容,使用System.in输入


这个案例手敲了,在获取每一位数字的时候我坑了一下,我以为有函数可以帮我获取…原来是自己敲代码获取

IDEA快捷键

ctrl+alt+T是显示if、while等循环语句的快捷键
自动补全变量名及类型,选中代码段,再 按 Ctrl + Alt + v
ctrl+D向下赋值一行
StringBuilder约等于容器,创建后的内容是可变的
StringBuilder的使用场景:字符串拼接和反转
shift+alt可以整列编辑
好了,我来了!
纯手撸代码,我要把眼高手低的坏习惯给改了!!!!!

截取字符串

需求:

代码:

public class SubString1 {
    public static void main(String[] args) {
        String str2 = new String("18189786590");
        String start =str2.substring(0,3);
        String end = str2.substring(7,11);
        System.out.println(start+"****"+end);
        System.out.println(str2.substring(9));
        System.out.println("================================================");
        String id = new String("622301200011010521");
        String year = id.substring(6, 10);
        String month = id.substring(10, 12);
        String day = id.substring(12, 14);
        char g =  id.charAt(16);

        System.out.println("人物信息为:");
        System.out.println("出生年月日:"+year+"年"+month+"月"+day+"日");

        System.out.println("性别为:"+gender(g));

    }
    public static char gender(char g){
        if(g%2 == 0){
            g = '女';
        }else{
            g ='男';
        }
        return g ;
    }
}

StringBuild:


需求一:

代码:

/*
**我在这加了个交互哈哈哈,感觉这样好玩一点!
*/
    public static void main(String[] args) {
        Scanner sc1 = new Scanner(System.in);
        System.out.println("你想玩对称字符的游戏吗?\n 有一种对称的字符串——————从左到右读和从右到左读得到的结果是一样的。\n例如1357531就是对称字符串,而adcadc不是对称字符串\n如果想,请输入:我想");
        if(sc1.next().equals("我想")){
            System.out.println("(如果想退出,请输入:我不想玩了)\n");
                    while (true) {
                        Scanner sc = new Scanner(System.in);
                        System.out.println("\n请输入一个字符串:");
                        String str = sc.next();
                        if(str.equals("我不想玩了")) break;
                        StringBuilder sb = new StringBuilder(str);
                        if(str.equals(sb.reverse().toString())){
                            System.out.println("它是对称字符串");
                        }else System.out.println("它不是对称字符串");
                    }

            System.out.println("再见");

        }else System.out.println("请自便");

        

    }

需求二:

代码:

    public static void main(String[] args) {
    
        int[] arr = {1,2,3,44,55,66};
        toStrings(arr);
  
  }

    public static String toStrings(int[] arr){
        StringBuilder sb = new StringBuilder("[");
        String str = new String();

        for (int i = 0; i <arr.length; i++) {


            //sb.toString() = sb.toString()+arr[i]+", ";
            if(arr.length-1 == i){
                sb.append(arr[i]);
            }else sb.append(arr[i]+", ");
        }
        System.out.println(sb.toString()+"]");

        return sb.toString();
    }

这个需求二啊不方便,还得我自己写判断语句来搞得括号,逗号,空格什么的,JDK8:不用啦老铁,直接用我们的StringJoiner类就OK啦
来瞅瞅:

可谓是雪中送炭啊!
时间不早了,今天就到这吧
明天接着玩!


如果是没有变量参与的字符串拼接,那么在编译的时候就已经拼接好了。
如果有变量参与,在JDK8以前会使用StringBuild进行拼接,此拼接都会新建一个StringBuilder对象,很浪费内存,

在JDK8之后,回先对要拼接的字符串长度进行预估,然后用一个数组来存储,但是每次判断字符串的长度也需要时间,
所以在字符串拼接时,如果是变量拼接则尽量不用+,因为一旦拼接的量很大,则效率会很低,
StringBuilder是一个内容可变的容器


练习:

代码:

public class RomaNum {
    public static void main(String[] args) {
        while (true) {
            System.out.println("转换罗马数字,请输入");
            Scanner sc = new Scanner(System.in);
            String  rt = sc.next();		//接手字符串
            String result = "";			//用来存放结果的字符串
            String[] rom = {"","Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ"};

            if(rt.length()>10){
                System.out.println("长度应小于等于9");
            }else {

                for (int i = 0; i < rt.length(); i++) {		//遍历字符串
                if(rt.charAt(i)>='0'&&rt.charAt(i)<='9'){	//判断是否全为数字
                    int a =Integer.valueOf(rt.charAt(i));	//将字符转为数值

                    result = result+rom[a-48];				//因为转为数值就变为ascii,所以要减个48,就变为了原来的数值

                }else{
                    System.out.println("只能输入数字!");
                    break;
                }
            }
                System.out.println(result);				//打印结果


            }

        }


    }

}

内部类

内部类

小结

阶段项目

常用API

常见算法


在使用public static void sort(数组,排序规则)这个方法时注意事项:

第二个参数是一个接口,所以我们在调用方法的时候,需要传递这个接口的实现类对象,作为排序的规则。//但是这个实现类,我只要使用一次,所以就没有必要单独的去写一个类,直接采取匿名内部类的方式就可以了

        Integer[] arr = {2, 3, 1, 7, 9, 5};
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;

            }
        });
//sort方法的底层原理:
//利用插入排序 + 二分查找的方式进行排序的。
//默认把0索引的数据当做是有序的序列,1索引到最后认为是无序的序列。
//遍历无序的序列得到里面的每一个元素,假设当前遍历得到的元素是A元素
//把A往有序序列中进行插入,在插入的时候,是利用二分查找确定A元素的插入点.
//拿着A元素,跟插入点的元素进行比较,比较的规则就是compare方法的方法体
//如果方法的返回值是负数,拿着A继续跟前面的数据进行比较
//如果方法的返回值是正数,拿着A继续跟后面的数据进行比较
//如果方法的返回值是0,也拿着A跟后面的数据进行比较
//直到能确定A的最终位置为止。


//compare方法的形式参数:
//参数一 o1:表示在无序序列中,遍历得到的每一个元素
//参数二 o2:有序序列中的元素


//返回值:
//负数:表示当前要插入的元素是小的,放在前面
//正数:表示当前要插入的元素是大的,放在后面
//0:表示当前要插入的元素跟现在的元素比是一样的们也会放在后面

lambda作用就是简化匿名内部类的书写

集合进阶

双列集合(有键值对),键不可以重复,值可以重复,键和值之间是一一对应点的

Map是最顶层的接口



用put方法添加元素,如果键不存在,则会直接添加,返回null;如果键存在,则会覆盖原来的键值,并将原来的值进行返回值。
添加删除操作的返回值都是值。


通过keySet()方法将集合中的键都放在一个集合中:Set<String> keys = map.keySet();
遍历单列集合的三种方法:keys.iterator()、增强for、keys.forEach()
用get(key)方法可以获取键对应的值String s1 = map.get(key);

HashMap中键要唯一,不太能通过索引获取元素的值。
无序、不重复、无索引(这些都由键决定)


异常类

开始异常的学习
异常不是不让程序出错,而是出了错之后如何处理,分为两大类,error和exception(异常)。





异常的两个作用:
异常作用一:异常是用来查询bug的关键参考信息
异常作用二:异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况

    public void setAge(int age) {
        if(age < 18 || age > 40){
            //System.out.println("年龄超出范围");
            throw new RuntimeException();           //在这抛出一个运行时异常对象
        }else{
            this.age = age;
        }
    }


以后程序报错就不能叫报错了,应该叫异常啦!!

   ※ JVM默认处理异常的方式:
        1. 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
        2. 程序停止执行,异常下面的代码不会再执行了
   ※  自己处理就是捕获异常,用try、catch
   ※ 交给调用者处理就是抛出异常,throw


如下代码,如果不用try、catch则会报异常,但是我们自己捕捉,自己处理这种异常,便不在报错。捕获异常好处:可以让程序继续往下执行,不会停止。需要注意的是catch的参数是异常的类名,所以我们要记住常见的异常名,比如数组越界啊,算术异常啊等等。

        System.out.println("狂踹瘸子那条好腿");
        try{
            System.out.println(2/0);
        } catch(ArithmeticException ae){
            System.out.println("没事");
        }
        //算术异常 ArithmeticException

        System.out.println("是秃子终会发光");
        System.out.println("火鸡味锅巴");


但是如果交换一下位置,便不会抛出异常:


public void printStackTrace()的细节:仅仅是打印信息,不会停止程序运行




运行时异常继承RuntimeException,编译时异常继承Exception

文件

IO流

多线程与并发

程序与进程的区别?
一个进程可以有多个线程
例如启动迅雷进程,然后下载多个文件这个操作也就是启动多个线程并运行
例如坦克大战,每一个坦克都是一个线程,他们发出的子弹也具有生命周期
并发和并行?
并发是针对单核cpu来说,同一时刻多个任务交替执行,来回切换,就跟人边开车边打电话一样,我们只有一个大脑。
并行是针对多核cpu来说, 同一时刻多个任务同时执行,

创建线程的两种方式:

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法(常用于A类已经继承了B类,但同时它也要创建进程,又继承了Thread,但Java不支持多继承,所以就实现一个接口吧)

    有上图可知,Thread类实现了Runnable接口

小作业:创建一个小猫线程,一个小狗线程,小猫执行8次便退出,小狗一直执行

package com;

public class CfyThread {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
       
        Dog dog = new Dog();
        dog.run();		//Thread没有start方法,看了眼源码只有run方法
    }
}
class Cat extends Thread{   	//用继承Thread类的方式

        int times = 0;

        @Override
        public void run () {

            while (true) {
                System.out.println("喵喵,我是小猫咪" + (++times));
                try {		//抛出异常,
                    Thread.sleep(1000);	//休眠一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(times==8){
                    break;
                }
            }

        }

}
class Dog implements Runnable{	//	用实现Runnable接口的方式

    int times = 0;

    @Override
    public void run () {

        while (true) {
            System.out.println("汪汪,我是修狗狗" + (++times));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}


整个过程的顺序以及示意图

并且在Java中,如果主线程提前结束了,而其他线程没有结束,那么整个进程也不会结束,如:

package com;
/*
Cat线程是继承Thread类,Dog线程是实现Runnable接口,YuanMeng也是继承Thread类
经过打印线程名称,知道了狗线程是主线程main,并且设置Dog线程提前结束,实验结果便是main线程提前结束,而Thread-0和Thread-1还在执行
发现一个有趣的现象,当我把Dog放在最前面时,后面两个线程不会执行,而是等Dog线程执行完才会执行
所以我觉得,在程序当中写定的线程的执行顺序会影响它们的执行过程
 */
public class CfyThread {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
        Cat cat = new Cat();
        cat.start();
        YuanMeng yuanMeng = new YuanMeng();
        yuanMeng.start();


    }
}
class Cat extends Thread{

        int times = 0;

        @Override
        public void run () {

            while (true) {
                System.out.println("喵喵,我是小猫咪" + (++times)+"。  线程名称:"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(times==80){
                    break;
                }
            }

        }

}
class Dog implements Runnable{

    int times = 0;

    @Override
    public void run () {

        while (true) {
            System.out.println("汪汪,我是修狗狗" + (++times)+"。  线程名称:"+Thread.currentThread().getName());
            try {                     //在这加休眠的原因是一秒计算机能执行的次数太多了,我们为了减少次数,所以适当休眠
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times== 6) break;
        }
    }

}
class YuanMeng extends Thread{

    int times = 0;

    @Override
    public void run () {

        while (true) {
            System.out.println("喵喵汪汪~,我是小原梦" + (++times)+"。   线程名称:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times==80){
                break;
            }
        }

    }

}


韩老师的这张示意图,告诉我们,主线程可以创建多个子线程,同时子线程又可以创建自己的子线程,只有所有的线程结束了,进程才能结束~

Java最难的一部分:多线程与高并发


run方法与start方法

兄弟萌,韩老师帮我解决了我上面那个有趣的现象,因为run方法是一个普通的方法,他并没有真正滴启动一个线程,他相当于就是main线程,调用它,会将run方法中的内容执行完毕才继续执行下面的代码, 而start方法一执行就会立刻再创建一个线程,妙啊

对start()进行追根溯源,会找一个start0方法,不过这个方法我们调用不了,它是本地方法,是JVM调用的,所以真正实现多线程方法的是start0方法.

但是我在用Runnable方法创建线程时,他并没有start方法啊,只有run方法,但是run方法又不能创建线程,我如何让他可以创建线程呢?看了一眼韩老师的代码,我似乎有点懂了

//Thread.java
//这是构造方法,卡一看到里面有一个形参对象正是Runnable,一个字妙!
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
   }

韩老师做了一个基于多线程的售票小系统,分别采用继承类与实现接口的方式完成这个小系统,最后结果是,都会进行超卖,票一共一百张,最后的余票竟然成了负数,其原因就是没有实现进程互斥,也就是三个进程同时拿到了数据,然后进行了修改,解决方法就是上个锁,用我们操作系统老师的原话就是,就好比你去上公共厕所,进厕所门的第一件事不是…,而是立刻锁门,不得不说还是挺形象的。

线程不能一直执行吧,当我们需要它结束的时候他就要结束,所以如何通知线程结束呢?其实本质就是用变量与循环的判断去控制,例如在main线程中创建一个子线程t1,当我们要求t1结束时,可以在t1的类中定义一个boolean类型的变量,然后当其为true时,t1一直处于执行状态,当其为false时就退出,然后在main线程中调用set方法设定参数为false就可以将其结束啦

更多推荐

Java基础教程(自学用,持续更新)