JAVA学习笔记

admin 发布于 25 天前 53 次阅读


此笔记写于我在大一时期的笔记,先存储于博客上,自己的理解偏多,不对的地方欢迎各位指正

JAVA学习笔记

变量

局部变量和全局变量

全局变量有默认值,而局部变量即方法中的变量无默认值,因此方法中的变量必须进行复制才能引用

局部变量前面不能加修饰符(public,private,等)

变量类型注意

  • JAVA中默认的小数常量为double类型,若需要float类型,则在数字末尾添加f或者F
  • 当类型为double或是float时,0.***中的0可省略,例如 0.1 可写成 .1

自动转换类型

低精度到高精度可自动转换

转换规则:

eg:

 int num = "a"; //a是由char类型转换为int类型,从低精度转换为高精度  char -> int
 double d = 80  // int -> double

自由转换数据类型

转为字符串

只需要在数值后面加上 "" 即可

eg:

 int a = 10;
 String b = a + "";

String类型转其它

需要引入Integer类

eg:

 //转为Int
 String a = "aa";
 int b = Integer.parseInt(a);
 //转为double
 double c = Double.parseDouble(a);
 //以此类推即可

运算符

+-*/不再赘述

%取余

a % b => a - a / b * b

前++和后++

作为独立语句时

i++和++i作为独立语句时据等价于i = i + 1

作为非独立语句时

i++表示先赋值后运算

++i表示先运算后赋值

eg:

 int a = 1;
 int b = a++;  //等价于 b = a,a = a + 1
 int c = 1;
 int d = ++c;  //等价于 d = c + 1,c = d或c = c + 1

三元运算符

基本语法

条件表达式?表达式1:表达式2

运算规则

1.若条件表达式返回为True,则执行表达式2 2.若条件表达式返回为False,则执行表达式2

 int a = 1;
 int b = 2;
 int result = a > b?a++:b--;
 // 返回result=1
 // 这里原来的a,b变量仍然不会改变

Switch

如下代码,表达式中返回一个结果,case中匹配常量和结果,若相同,则执行同级case下的语句,否则匹配下个case,若没有相同的常量,则默认执行default下的语句

表达式的返回值必须是 byte short int srting char enum

case后的语句必须是常量或是 常量表达式,不能是变量

当执行完一个case语句没有break语句时,会顺序执行到switch结尾,直到遇到break,注意,期间不管case是否匹配,都会执行下面的语句

 switch(表达式){
     case 表达式结果等于这里的常量时:
     // 语句1
     break;
     case 常量2:
     // 语句2
     break;
     default:
     // 语句三
     break;
 }

do...while

与while不同的是,while是先判断在执行循环体,do...while是先执行循环体在判断

while不一定执行循环体,do...while至少执行一次循环体

 int count = 0;
 do{
     count++;
     System.out.println(count);
 }while(count<=10);

break

细节

通过lable可控制具体跳出的循环体

若不指定跳出的循环体,则默认跳出离break最近的循环体

lable1:{}

break lable1;

lable1:
for(){
lable2:
while(){
lable3 do{

}while();
break lable1;
break lable2;
break lable3;
}
}

递归函数

强烈推荐使用内存分析法

下面是案例分析

阶乘案例

class A{
public int factorial(int num){
if n == 1{
return 1;
}else{
return factorial(num - 1) * num;
}
}
}

A test = new A();
int ret = test.facaorial(5);
System.out.println(ret); // 返回结果是120

分析

首先传入参数5,进入到第6行的factorial函数中,然后再在栈中新开辟一个内存空间假设为t1,t1的sum为4,然后再在执行factorial函数,在开辟t2,t2sum为3,....t4sum为1,所以t4返回值为1,t4空间销毁回收,返回给t3,即1sum,t3的sum为2,即1*2,返回给t2,返回值为2,然后2*3返回为6,t1是6*4返回给第一次计算的空间即24*5,所以最后结果为120

可变参数

当一个方法接收的参数类型一样且为多个时,可以用可变参数接收

public void static main(String[] args){
class sum{
public int sum(int...nums){
# nums的数据类型相当于数组
System.out.println(nums.length);
# 求和
int res = 0;
for(int i = ;i < nums.length ; i++){
res += nums[i];
}
}
}
}

注意

  • 可变参数可以接受数组
  • int...nums 中int为数据类型,... 不可动,nums为形参
  • 普通形参和可变参数放在一起时,可变参数要放在普通形参后面
  • 一个方法中只能有一个可变参数

构造器

注意事项

  • 格式: [修饰符] 类名(形参){}
  • 不能有return语句
  • 构造器可以重载
  • 构造器中如果有This语句,则This语句必须放在第一条
class Person(){
String name;
int age;
public Person(String pname,int page){
name = pname;
age = page
}
}
Person p = new Person("名字",10);
System.out.println(p.name + p.age);
# 名字10

This,Super关键字

Super只能在构造器中使用,且This和Super不能同时使用

This相当于Python整的self,在类中则代表当前对象

Java示例如下

class Dog{
String name;
int age;
public Dog(String name,int age){
this.name = name;
this.age = age;
}
public void t1(){
System.out.println("t1()方法被调用");
}
public void t2(){
// 调用t1的方法
this.t1();
}
}

Python示例如下

class Dog:
def __init__(self,name, age):
self.name = name
self.age = age
def t1():
print("t1()方法被调用")
def t2():
# 调用t1方法
self.t1()

访问构造器

注意

  • 访问构造器语法 this(); 必须放在构造器第一行
class Dog{
String name;
int age;
public Dog(){
this("小明", 20);
}
public Dog(String name, int age){
this.name = name;
this.age = age;
System.out.println(this.name + this.age);
}
}

Dog d = new Dog();
// 输出 小明20
  • this(name)继承中代表访问一个参数只有name的构造器
class Person {
public static void prt(String s) {
System.out.println(s);
}

Person() {
prt("父类·无参数构造方法: "+"A Person.");
}//构造方法(1)

Person(String name) {
prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
}//构造方法(2)
}

public class test extends Person {
test() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类无参数构造方法: "+"A chinese coder.");
}

test(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类含一个参数的构造方法: "+"his name is " + name);
}

test(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}

public static void main(String[] args) {
test cn = new test();
// cn = new test("codersai");
cn = new test("codersai", 18);
}
}

// 输出如下
//父类·无参数构造方法: A Person.
//子类·调用父类无参数构造方法: A chinese coder.
//父类·含一个参数的构造方法: A person's name is codersai
//子类·调用父类含一个参数的构造方法: his name is codersai
//子类:调用子类具有相同形参的构造方法:his age is 18

Super

super.属性可以访问跟父类的属性,但不能访问父类的私有属性

spuer.方法名(参数列表)可以访问父类的方法,但是不能访问父类私有的方法

super()必须放在子类构造器的第一行,且不能与this同时存在

访问修饰符

类中的变量,函数都可用访问修饰符

类也可以用访问修饰符,不过只能同 public 和 默认

继承

关键词 extends

注意事项

  • 子类继承了所有的属性和方法,但是私有属性和方法不能在子类直接访问,要通过公用的方法去访问
  • 当创建子类对象时,不管子类使用哪个构造器,默认情况下会去调用父类的无参构造器,如果父类中没有无参构造器,则必须在子类的构造器中用super()去指定父类哪个构造器来完成对父类的初始化工作,否则编译不通过。

多态

多态因为可以理解为多种形态

方法的多态

方法的重载,重写实际上就是多态的一种

对象的多态

1.一个对象的编译类型和运行类型可以不一致 2.编译类型在定义对象时,就确定了,不能改变了 3.运行类型是可以改变的 4.编译类型看定义时 = 号 的左边,运行类型看 = 号 的右边

class Animal{  //父类
private String Name;
public void setName(String Name){
this.Name = Name;
}
public String getName(){
return Name;
}
}
class Dog extends Animal{ //子类
private String Name = "Dog";
public String getName(){
return Name;
}
}
class Cat extends Animal{ //子类
private String Name = " Cat";
public String getName(){
return Name;
}
}


public static void main(String[] args){
Animal dog = new Dog(); //dog的编译类型是Animal,运行类型是Dog
dog = new Cat(); //此时dog的编译类型还是Animal,运行类型变成了Cat
}

对象的多态,就是在调用类的时候传入的参数可以是一个对象,通过对象去调用其子类或本类的方法 eg:

public static void main(String[] args){
Animal dog = new Dog();
Animal cat = new Cat();
call(dog); //输出Dog is runing
call(cat); //输出Cat is runing
}
public void call(Animal name){
System.out.println(name.getName + " is runing");
}

向上转型

向上转型就是Animal dog = new Dog() dog去调用Dog类和Animal类的方法,弊端是假如说Dog中有A方法可以调用,Animal中的A方法为私有方法或者没有此方法,那么就无法编译通过。虽然是从子类到父类去寻找方法,但是由于Java特性,无法编译通过,于是有了向下转型

向下转型

书接上回,dog如何调用Dog类中独有的方法呢?就用到了向下转型,也可以理解为强转,转的 不是编译类型,因为编译类型确定后就无法改变,转的是运行类型。

Animal dog = new Dog();
Dog dog1 = (Dog) dog;

这样dog1对象就能调用Dog类的方法了。

注意

  • 语法 子类类型 引用名 = (子类类型)父类引用;
  • 要求父类的引用必须指向的是当前目标类型的对象(也就是说上方代码第二行最后一个的dog的运行类型必须是dog1的编译类型
  • 变量不能重写,以向上转型为例,Animal animal = new Dog(); System.out.print(amimal.name) 假设父类Animal和子类Dog都有公共的变量name,则以编译类型的变量为主,打印出来的就是Animal的name。
  • 属性看遍历类型,方法看运行类型。

instanceof

语法: 对象A instanceof 类B 返回布尔值

判断对象A的运行类型的类是否为类B或类B的子类,若是则为True,反之为False

动态绑定机制

  1. 当调用对象方法时候,该方法会和该对象的内存地址/运行类型绑定
  2. 当调用对象属性时,没有动态的绑定机制,哪里声明哪里使用。
class A {  //父类
public int i =10;
public int sum(){
return getI + 10;
}
public int sum1(){
return i + 10;
}
public int getI(){
return i;
}
}
class B extends A { //子类
public int i = 20;
public int sum(){
return i + 20;
}
public int getI(){
return i;
}
public int sum1(){
return i + 10;
}
}
// main方法
A a = new B(); //向上转型
System.out.println(a.sum()); //40
System.out.println(a.sum1()); //30

!!!

如果删除掉子类B的sum()方法,则调用a.sum()时候会继承A的sum()方法,返回的时getI() + 10,而由于动态绑定机制,getI()指向的是a的运行类型也就是B类中的getI()方法,所以此时返回的是30

多态数组

定义:数组的定义为父类类型,厘米那的实际元素类型为子类类型。

代码块

代码块和方法不同没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显式调用,而是在加载类时,,或创建类时隐式调用

基本语法

[修饰符]{
Code
};

注意

  • 修饰符为可选项,要写的话,也只能写static
  • 代码块分为两类,静态代码块即用static修饰的代码块和普通代码块即没有修饰符的
  • 末尾;可写可不写

代码块什么时候被加载

  1. 创建对象实例时
  2. 创建子对象实例时候,父类的代码块也会被加载(父类的代码块先加载,接着加载子类的代码块)
  3. 使用类的静态成员时(静态方法,静态变量)ps:使用类的静态成员时普通代码块即非静态代码块不会被执行

静态代码块只会被执行1次,非静态的没创建一次就会被执行一次

优先级

  • 静态代码块和静态属性的优先级一样
  • 倘若有多个静态代码块和多个静态属性,则按照书写顺序加载
class A{
private static int num = getNum();
static {
System.out.println("静态代码块初始化");
}
public int getNum(){
System.out.println("getNum方法被调用");
return 100;
}
}

new A();

!!!

当实例化A类时候,优先初始化第一个静态属性即num,此时num调用了getNum方法,所以优先输出getNum方法被调用,然后再初始化静态代码块

  • 静态代码块和静态属性的优先级高于普通代码块和普通属性
  • 普通代码块和普通属性的优先级一样
  • 构造器的优先级是最低的class AAA{
    public AAA(){
    //super();
    //调用本类的普通代码块
    System.out.println("AAA类的构造器被调用");
    }
    }
    class BBB extends AAA{
    {
    System.out.println("BBB的普通代码块被调用");
    }
    public BBB(){
    //super();
    //调用本类的普通代码块
    System.out.println("BBB类的构造器被调用");
    }
    }
    new BBB();每个类的构造器下都有隐藏的代码
    1. 调用super()
    2. 调用本类的普通代码块所以上述代码的运行结果就是 1 AAA类构造器 2 BBB普通代码块 3 BBB构造器
    总结执行顺序如下
    1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    3. 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
    4. 父类的构造方法
    5. 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
    6. 子类的构造方法

final关键字

结论

不能被继承,重写,修改里面的值

三种赋值方式

  1. 定义时
  2. 构造器中
  3. 代码块中如果final修饰的属性是静态的,那么只能在代码块和定义时赋值。

final修饰的类不能被继承,一般final修饰类后,方法就不用加final修饰了

final和static

final和static同时使用不会创建类、

class Test{
public final static int num = 555;
static{
System.out.println("静态代码块被调用");
}
}

假如只有static没有final,那么调用Test.num时候静态代码块也会被调用,若加上final,则不会调用静态代码块。

抽象类 abstract

抽象类就是没有方法体的方法,方便子类继承重写方法,抽象类和抽象方法的定义需要用到abstract关键字

当一个类中存在抽象方法时,该类必须声明为抽象类

abstract class Animal{
abstract public void eat();
}

细节

  1. 抽象类不能被实例化
  2. 抽象类不一定要有抽象方法
  3. abstract关键字只能修饰类和方法,不能修饰属性
  4. 如果一个类继承了抽象类,则必须实现抽象类的所有抽象方法,否则子类也要声明为抽象类
  5. 抽象方法不能用private,static,final关键字修饰,因为这些关键字是与重写相违背的

接口 interFace

基本语法

定义接口

//接口类中可省略abstract和public

public interface Method{
//属性
//方法(1.抽象方法 2.默认default方法 3.静态static方法)
void job(); //接口类中可省略abstract和public
}
//实现(重写接口中的方法)
public class Mian implenets Method{
public void job(){
System.out.println("Hello World");
}
}
public class T1(){
public void run(interface Method){
Method.job();
}
}
//调用
public class void T2{
public static void main(String[] args){
T1 tt = new T1();
Main mm = new Main();
tt.run(mm);
}
}

注意

  1. 如果一个类实现了一个接口方法,即 一个类 implenets 接口 则这个类必须实现这个接口的所有的抽象方法
  2. 在JDK8之后,可以有静态方法
  3. 如果用到非抽象方法,需要加上default的关键字
  4. 接口不能被实例化
  5. 一个类可以同时实现多个接口
  6. 接口中的属性,只能是final的,且为 public static final,假设我写了一个int x = 1,那么它就是public static final int x = 1;(必须初始化)
  7. 接口不能继承类,但是接口可以继承extends多个接口

内部类

局部内部类(有类名)

  1. 局部内部类可以直接访问外部类的所有成员对象,包括私有的
  2. 内部类不能用除final的修饰符修饰
  3. 如果外部类和局部内部类的成员名重名时,遵循就近原则,如果要调用外部类则需要 外部类名.this.成员名

匿名内部类(没有类名)

基于接口

首先定义一个接口innerTest,使其拥有一个test方法

以下是内部匿名类的用法,通过实例化一个接口并重写其方法来实现匿名内部类

下图是上图的底层源码

匿名内部类实际上是有名字的,我们看不到,系统自动去分配,而他的底层源码就是上图所示的形式

上图的编译类型是接口的innerTest类型,而他的运行类型则是个底层代码的te的类型

基于类

跟基于接口的类似

很牛逼的使用匿名内部类

匿名内部类可以直接当作一个参数使用

interface IA{
void test();
}
class main{
public static void method(IA i){
i.test();
}
public static void main(String[] args){
method(new IA(){
public void test(){
System.out.println("世界名画!");
}
});
}
}

// 输出 世界名画

细节

  1. 匿名类有两种调用模式,第一种就是上面使用的的,利用可动态绑定机制,第二种可以直接去调用class outer{
    new test(){
    public void test(){
    System.out.println("我重写了test函数");
    }
    }.test();
    }
    class test{
    public void test(){
    System.out.println("test");
    }
    }
  2. 外部其他类不能访问内部类

成员内部类

  1. 成员内部类是定义在外部类的成员位置
  2. 成员内部类可以访问外部类的所有成员,包括私有的
  3. 可以添加任意的访问修饰符,因为它本身就是一个成员
  4. 如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.this.方法名/属性名

外部其他类调用成员内部类

方法一
// 假设outer为外部类,inner为成员内部类
outer.inner test = new outer.inner();
方法二

在外部类中写一个方法返回内部类的对象

静态内部类

说明:静态内部类仍然定义在外部类的成员位置,只是多了个static修饰

  1. 可以直接访问外部类的静态成员,包括私有的,但是不能直接访问非静态的
  2. 因为他也是成员内部类,所以可以使用修饰符
  3. (不太一样,无this)如果外部类和成员内部类的方法或者属性变量重名,依然遵守就近原则,访问外部类:外部类.方法名/属性名

调用方法同上

枚举

自定义枚举

  1. 构造器私有化
  2. 创建一个静态成员new一个所需的对象,用static final修饰
class test{
public int i;
public static final test CREATEST1 = new test(1);
public static final test CREATEST2 = new test(2);
public static final test CREATEST3 = new test(3);
private test(int i){
this.i = i;
}
}
// static 和 final 一起使用时候会使类不会被加载
// 调用就直接 test.CREATEST1.XXX
// 枚举对象名一般大写

关键字enum

优化后的代码

enum test{
CREATEST1(1),CREATEST2(2),CREATEST3(3),WHAT;
public int i;
private test(int i){
this.i = i;
}
private test(){
}
}

注意

  1. 使用关键字enum代替class
  2. public static final test CREATEST1 = new test(1);直接使用CREATEST1(1)代替,若有多个则用逗号隔开
  3. CREATEST1(1) 括号里面是实参,对应构造器的形参
  4. CREATEST1(1)必须写在最前面
  5. 如果使用无参构造器,则可以省略 ( ) 直接写常量对象名
  6. 使用enum定义枚举时候,就不能在继承其他类了,因为它已经隐式的继承了enum类了,然仍然能实现接口

注解/元数据

@Override

该注解表示方法重写了父类的某个方法

该注解只能用在某个方法上

如果写了@Override注解,java编译器在编译时候会寻找父类中是否含有该方法,如果真的被重写,则编译通过。否则就抛出异常

@Deprecated

表示程序元素(类,方法)已经过时了

可以修饰 类 方法 参数 包 字段等等

过时并不能代表不能用

@SuppressWarnings

抑制编译器警告

用法

@SuppressWarnings{""}

{" "}里面写抑制的类型,all 代表一直所有警告

@Target

该注解是修饰注解的注解,称为元注解

集合

List

基本方法(实现了Collection的接口方法)

  • add:添加单个元素
  • remove:删除元素(可指定索引或指定删除的对象)
  • contains:查找元素是否存在
  • size:获取元素个数
  • isEmpty:判断是否为空
  • clear:清空
  • addAll:添加多个元素(可以放实现了Collection接口的对象,比如说集合)
  • containsAll:查找多个元素是否都存在(可以放实现了Collection接口的对象,比如说集合)
  • removeAll:删除多个元素(可以放实现了Collection接口的对象,比如说集合)

迭代器遍历

Iterator iterator = coll.iterator() //得到一个集合的迭代器

.hasNext()是否还有下一个元素 编译类型是Object

.next() 显示当前元素内容,并对指针进行下移

第二次遍历需要重置迭代器,否则就会抛出异常

iterator = coll.iterator() //重置迭代器

IDEA快速生成whlie循环遍历的快捷键:itit

IDEA快速生成增强for循环遍历的快捷键:I

增强for循环遍历

for(元素类型(Object) name:col){
System.out.println(clo);
}

Set

Set接口介绍

  1. 无序的(添加和取出的顺序不一样),没有索引
  2. 不允许有重复的元素,所以最多包含一个null

注意:取出的顺序虽然是无序的,但是是固定的,底层算法决定的

方法说明

因为set没有索引,故不能用get方法取出,遍历的话同上实现了List的接口的类一样,增强for循环和迭代器

  • add方法说明,add方法在这里有一个布尔类型的返回值,用来判断是否添加成功
  • remove 删除指定对象

Map

key:value

方法说明

  • put(key,value) 添加:key的值是唯一的不能重复,value的值可以重复,假如说key的值重复了,那么则会替换先前存在key的value的值(Hashtable的key和value都不能为空)
  • remove(key)根据键删除对应的映射关系
  • get(key)获取对应key的value 返回值类型为object
  • size()返回k-v的个数
  • isEmpty()判断是否为空
  • clear()清空键值对
  • containsKey(key)查找键是否存在
  • keySet() 返回一个set集合,集合里面是所有的key

遍历的六大方式

(比List和Set稍微复杂点,但是基本原理相同)

    1. 利用keySet()方法获取所有的key,然后用增强for循环get到每一个value
Map map = new HashMap();
map.put("no1","Lin");
map.put("no2","Kenen");
map.put("no3","f");
map.put("no4","z");
Set set = map.keySet();
for(Object i:set){
System.out.println(map.get(i));
}
    1. 迭代器
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(map.get(key));
}
    1. 增强for 同上
    2. 迭代器 同上
    3. entry
    Set set = map.entrySet();
    for (Object i:set){
    Map.Entry m = (Map.Entry) i;
    System.out.println(m.getKey()+"-"+m.getValue());
    }

Collections工具类

Collections类能对Set,List,Map等集合进行操作

常用方法

  • reverse(List) :反转列表
  • shuffle(List) :对列表进行随机排序
  • sort(List) :排序
  • sort(List,Comparatoe) :根据Comparator产生的顺序对列表排序
  • swap(List,int,int) :将指定List的集合的两个元素进行交换位置
  • copy(desp,list) :将list的内容拷贝到desp中。注意:desp的长度必须>=list的长度,都则会抛出异常
  • boolean replaceALL(List,old,new) :将List集合中的old值全部替换为new的值
  • frequency(Collection,Object) :返回指定集合中指定元素出现的次数

泛型

泛型的通配和继承

  1. 泛型不具备继承机制 List<Object> list = new list<String>(); //错误
  2. <?> 支持任意泛型
  3. <? extends A>支持A类及A类的子类,规定了泛型的上限
  4. <? super A> 支持A类及A类的父类,规定了泛型的下线

举例

public void test(List<?> list){
for(Object o:list){
System.out.println(o);
}
}
public void test(List<? extends AA> list){
for(Object o:list){
System.out.println(o);
}
}
public void test(List<? super AA> list){
for(Object o:list){
System.out.println(o);
}
}

多线程

创建线程的两种方式

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法

*为什么是开启新线程是start方法而不是run方法?

进入start方法可以看到,最主要开启的新的线程的方法是start0()方法,这个是JVM的内部方法,底层是c/c++编写,实际就是由底层创建了一个新的线程去调用了run方法

代码示例

//继承实现
public class Cat extends Thread{
@Override
public void run(){
int count = 0;
while (true){
System.out.println("我是小猫,喵喵喵"+ (++count));
if (count > 8){
break;
}
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
//实现Runnable接口实现
public class Dog implements Runnable{
@Override
public void run(){
int count = 0;
while (true){
System.out.println("我是小狗,汪汪汪"+ (++count));
if (count > 8){
break;
}
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
//主方法
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
cat.start();
Thread thread = new Thread(dog);
thread.start();
}
}

总结

继承了Thread的类可以直接调用start方法,但由于Java是单继承机制,所以可以实现Runnable接口,此时开启新线程就需要在创建一个新的Thread的类,并传入实例化的对象,然后调用其start的方法

区别

  1. 从Java的设计来看,继承Thread和实现Runnable没有本质的区别
  2. 实现Runnable的接口方式更适合多个线程共享一个资源的情况,并避免了单继承机制的限制
class Te implements Runnable{
public void run(){}
}
class main{
public static void main(String[] args){
Te t0 = new Te();
Thread t1 = new Thread(t0);
Thread t2 = new Thread(t0);
t1.start;
t2.start;
}
}

线程常用方法

  • setName:设置线程名称
  • getName:获取线程名称
  • start:开始执行线程
  • run:调用线程run方法
  • setPriority:更改线程优先级
  • setPriority:获取线程优先级
  • sleep:休眠
  • interrput:中断线程,不是结束线程 一般用于zheng'zai

线程插队

  1. yield:线程的礼让,让出CPU,让其他线程执行,但是礼让时间不确定,所以礼让不一定成功
  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所以任务(会阻塞线程(主线程也会被阻塞))

守护线程

引入

当子线程是一个无限循环的线程时,主线程退出,子线程并不会退出,为了此时让子线程也退出,就用到了守护线程

解决办法

线程名.setDaemon(true);

Synchronized

线程同步机制

  • 在多线程编程,一些敏感的数据不允许被多个线程同时访问,此时就是使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性。
  • 也可以理解成:线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存进行操作,知道该线程完成操作,其他线程才能对该内存进行操作

实现同步的具体方法-Synchronized

  1. 同步代码块
synchronized (对象){
// 这里放需要被同步的代码
}

public void m1(){
// Code
synchronized (this){
// Code
}
}
  1. Synchronized放在方法声明中,表示整个方法为同步方法
public synchronized void m1(){
// 这里放需要被同步的代码
}
注意

选择同步代码块的对象 this 是指本对象,如果多个new对象对其进行start,那么是锁不住的,所以推荐采用实现Runnable接口的方法实现多线程

线程的死锁

介绍

多个线程占用了对面的资源,但不肯相让,导致了死锁。

释放锁

会释放锁的操作
  1. 当前线程的同步方法,同步代码块执行完毕
  2. 当前线程在同步方法,同步代码块中遇到了return或break
  3. 当前线程在同步方法,同步代码块中遇到了Error或Exception,导致异常结束
  4. 当前线程在同步方法,同步代码块中执行了wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
  1. 当前线程在同步方法,同步代码块中执行了Thread.sleep() 或者 Thread.yield()方法
  2. 当前线程在同步方法,同步代码块时,其他线程调用了该线程的suspend()方法,该线程将会被挂起,不会被释放

四大核心函数式接口

函数式接口称谓参数类型用途
Consumer<T>消费性接口T对类型为T的对象应用操作,包含方法void accept(T t)
Supplier<T>供给型接口返回类型为T的对象,包含方法T get()
Function<T,R>函数型接口T对类型为T的对象应用操作,并返回结果,结果是R类型的对象,包含方法R apply(T t)
Predicate<T>判断型接口T确定类型为T的对象是否满足约束,并返回boolean值,包含方法boolean test(T t)
 public class MyTest {
 ​
     // Consumer Interface Demo
     // 和 JavaScript 的 CallBack 有异曲同工之妙
     public void consumer(Integer num, Consumer<Integer> consumer) {
         consumer.accept(num);
    }
 ​
     @Test
     public void testConsumer() {
         // 可以这么理解 consumer(x,callback)
         consumer(500,num -> System.out.println("The number is " + num));
    }
 ​
     // Supplier Interface Demo
     public <T> T supplier(Supplier<T> supplier) {
         return supplier.get();
    }
 ​
     public String hello(){
         return "hello";
    }
 ​
     @Test
     public void testSupplier() {
         System.out.println(supplier(() -> hello()));
    }
 ​
     // Function Interface Demo
     public <T,R> R function(T t,Function<T,R> function){
         return function.apply(t);
    }
 ​
     @Test
     public void testFunction() {
         String function = function(100, num -> String.valueOf(num));
         System.out.println(function);
    }
 ​
     // Predicate Interface Demo
     public <T> void predicate(T t,Predicate<T> predicate) {
         System.out.println(predicate.test(t));
    }
 ​
     @Test
     public  void testPredicate() {
         predicate(100,num -> num == 1000);
    }
 }