java基础大全--呕心沥血编写--耗时一个周(还望各位大神不吝赐教)

文章目录

一、JAVA简介:

  1. JAVA之父-詹姆斯.高斯林(“高司令”)
  2. JAVA优势:(1)跨平台(“一次编译,到处运行”);(2)健壮,安全;(3)分布式;(4)多线程;(5)面向对象;(6)高性能。
  3. 环境变量的设置:(1)添加JAVA_HOME环境变量,指向JDK安装目录;(2)修 改path: 用于指定操作系统的可执行指令的路径。
  4. JDK的安装及java环境。JDK:java开发工具集;JRE:java运行环境。

二、DOS命令

 win+R:敲“cmd”回车进行如下操作
 1.切换目录:cd /d 目标目录
 2.编译java源代码:javac -d . java源文件名称
 3.运行.class文件(字节码文件):java 包.class类名

三、变量

每次变量使用前必须要先申明,然后赋值,方可使用。eg:int i=20;

四、数据基本类型与引用类型:

在这里插入图片描述
八大基本类型:byte、short、int、long、float、double、char、boolean
封装类:Byte、Short、Integer、Long、Float、Double、Character、Boolean
优先级:byte<short<char<int<long<float<double

  1. 自动转型:小范围转换成大范围数据。
    大范围数据类型 变量名=小范围数据类型的值
    eg:long=5;
  2. 强制转型:大范围数据类型转换成小范围数据。
    小范围数据类型 变量名=(小范围数据类型)大范围数据类型的值;
    eg: int a=(int)5L;
  3. java语言整型常量默认为int;java浮点型常量默认为double型;char字符本质是数字; boolean类型不能转换成任何其它数据类型。
  4. 转义字符:
    在这里插入图片描述

五、运算符

1. 易混淆运算符

  1. 自增运算符:++
    a++:先保存旧值,再自增,用旧值参加运算;++a:直接自增,用自增后的值参与运算。
  2. 短路与&&: 如果前面的判断结果为false,则后面的判断不再执行;非短路与&:每一格判断逻辑都要执行
  3. 短路或|| :如果前面的判断结果为true,则后面的判断不再执行;非短路或|:每一个判断逻辑都要执行。

2. 位运算符:

在计算机中数字的运算都是按照“补码”的形式运算的

  1. 正数的原码、反码、补码、是相同的。
  2. 负数的反码是其原码除了符号位以外的二进制位取反,其补码是反码加1。
  3. 符号位是最左边的那个二进制位,0代表正数,1代表负数。
  4. 按位与:& 两个二进制位都为1,结果才为1,其余结果为0;
  5. 按位或:| 只要有一个二进制位为1,结果就为1,而两个二进制位都为0,则结果为0;
  6. 按位异或:^ 相同的两个二进制结果为0,不同的二进制结果为1;
  7. 按位取反:~0变1,1变0。
  8. 右移:>>右移后,左边的二进制位用符号位来补,正数补0,负数补1;
  9. 左移:<<左移后,右边的二进制位用0来补;
  10. 无符号右移:>>>右移后,左边的二进制位用0来补。

3. 三目运算符:

结果返回boolean值的表达式?“结果为true时内容”:“结果为false时内容”。

五、语句

六、集合类

Java.util包中提供了一些集合类,常用的集合类有List集合、Map集合、Set集合继承关系如下:
在这里插入图片描述

1. Collection接口

常用方法如下:
在这里插入图片描述
由于Set集合和List集合都继承了Collection接口,所以这些方法Set和LIst都是能用的。

public static void main(String[] args) {
    System.out.println("Hello World!");

    Collection<String> collection=new ArrayList<String>();

    ((ArrayList<String>) collection).add("hello");
    ((ArrayList<String>) collection).add("world");

    Iterator<String> iterator=collection.iterator();

    while (iterator.hasNext())
    {
        System.out.println(iterator.next());
    }

    System.out.println("长度:"+collection.size());

    System.out.println("删除hello,还有:");
    collection.remove("hello");

    Iterator<String> iterator1=collection.iterator();

    while (iterator1.hasNext())
    {
        System.out.println(iterator1.next());
    }

    System.out.println("再删除world,剩余长度:");
    collection.remove("world");

    System.out.println("长度"+collection.size());
}

运行结果:
在这里插入图片描述

2.List集合

list集合实现了Collection接口并且定义了两个非常重要的方法:

  1. get(int index);//获得索引位置的元素
  2. set(int index,Object obj);//将索引位置的元素替换为obj
    在这里插入图片描述

3.补充:数组

Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。
Array获取数据的时间复杂度是O(1),但是要删除数据却是开销很大,因为这需要重排数组中的所有数据。

1) 数组及相关概念。

  1. 数组:一组相同类型的数据的集合。类似于一个容器,里面存放一堆类型相同的数据。
  2. 数组元素:数组中存放的每一个数据,都是一个 数据元素。
  3. 数组长度:数组中数据元素的个数,可用数组名.length来表示。
  4. 数组下标:数组中元素的表示符,根据序号标记位置,数组下标从0开始,长度为变量名.length。

2) 数组的创建。

  1. 数组的声明。
    格式: 类型名[] 变量名;
    如:int[] arr;//定义一个数组a,存放一组int类型的数据
  2. 数组的初始化。
    (1). 静态初始化:指定长度和数组元素。
    格式:
    数据类型[] 数组名 = {元素1,元素2,…};
    如:int[] arr= {1,2,3,4,5,6,7,8,9,0,};
    (2). 动态初始化
    格式1: 只指定长度,由系统给出初始化值
    数据类型[] 数组名 = new 数据类型[数组长度];
    如: int[] arr = new int[5];
    格式2:指定元素,但[]内不能规定长度。
    数据类型[] 数组名=new 数据类型[]{元素1,元素2,元素3,元素4…};
    如:int[] arr=new int[]{1,2,3,4,5,6,7,8,9,0};

3) 数组的遍历

  1. for循环遍历
    在这里插入图片描述
    在这里插入图片描述
  2. Arrays.toString遍历
    输出格式为:[元素1,元素2,元素3,…].
    在这里插入图片描述
    PS:注意:使用时必须要导包import java.util.Arrays;
  3. foreach遍历
	格式:for (String string : array) {
	System.out.println(string);
	}

4. 数组的引用。

数组属于引用型变量,因此两个相同类型的数组如果具有相同的引用,它们就有完全相同的元素。
(1) 数组间的赋值:将一个数组赋值给另一个数组。
在这里插入图片描述
在这里插入图片描述
PS:由上可得,当一个数组赋值给另一个数组之后,改变其中任意一个数组中元素的值,另一个数组中相应元素的值也会随之变化。
(2) 数组的复制:将一个数组的元素复制到另外一个数组中。

  1. for循环复制。
    在这里插入图片描述
  2. 系统复制法
    System.arraycopy(src,begin1,des,begin2,length);
    在这里插入图片描述
  3. Arrays.copyOf(src,newLength);
    Src:源数组,表示被复制的数组
    newLength:复制后新数组的长度。
    例:
    在这里插入图片描述
    PS:与数组的复制不同,当一个数组复制到另一个数组后,干煸其中一个数组元素的值,不会影响另一个数组元素的值。

5) Arrays工具类。

  1. toString方法是把数组转换成字符串进行输出。(参数是数组,返回的是字符串)
  2. copyof把一个数组复制出一个新数组(新数组的长度为length)。
  3. sort方法,把数组中的元素按升序排序。【参数:除了布尔型都可以,类也可以】
  4. BinarySearch:找到元素在数组当中的下标。

1. Set集合

set集合中的对象不按特定的方式排序,且不能包含重复元素。
在这里插入图片描述
在这里插入图片描述
对于TreeSet,存进treeSet中的对象必须实现Comparable接口,并重写comparaTo(Object obj)方法用来比较此对象与指定对象的顺序,如果小于、等于或大于,则分别返回 负整数、0、1.
案例:

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
 
public class Student implements Comparable {
 
 
    private int id;
    private String name;
 
    public Student(int id,String name) {
        this.id=id;
        this.name=name;
    }
 
    public String getName() {
        return name;
    }
 
    public int getId() {
        return id;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    @Override
    public int compareTo(Object o) {
 
        Student student=(Student)o;
        return id>student.id? 1:(id==student.id? 0:-1);
    }
 
    public static void main(String args[])
    {
        Student stu1=new Student(001,"张三");
        Student stu2=new Student(002,"李四");
        Student stu3=new Student(003,"王五");
 
 
        TreeSet<Student> tree=new TreeSet<Student>();
 
        tree.add(stu1);
        tree.add(stu2);
        tree.add(stu3);
 
        Iterator it=tree.iterator();
 
        while (it.hasNext())
        {
            Student student=(Student) it.next();
            System.out.println("id:"+student.getId()+" "+"name:"+student.getName());
        }
 
        System.out.println("第一个元素:"+"id:"+tree.first().getId()+" "+"name:"+tree.first().getName());
 
 
        System.out.println("最后一个元素:"+"id:"+tree.last().getId()+" "+"name:"+tree.last().getName());
 
 
        System.out.println("stu3之前的所有元素");
        Iterator<Student> iterator=tree.headSet(stu3).iterator();
        while (iterator.hasNext())
        {
            Student student=(Student) iterator.next();
            System.out.println("id:"+student.getId()+" "+"name:"+student.getName());
        }
 
        System.out.println("stu2之后的所有元素");
        Iterator<Student> iterator1=tree.tailSet(stu2).iterator();
        while (iterator1.hasNext())
        {
            Student student=(Student) iterator1.next();
            System.out.println("id:"+student.getId()+" "+"name:"+student.getName());
        }
 
        System.out.println("stu1和stu3之间的元素");
        Iterator<Student> iterator2=tree.subSet(stu1,stu3).iterator();
        while (iterator2.hasNext())
        {
            Student student=(Student) iterator2.next();
            System.out.println("id:"+student.getId()+" "+"name:"+student.getName());
        }
    }
 
}

在 add 数据的时候,无论数据如何颠倒,下面输出顺序不变。 按自然顺序排序 。
输出结果:
在这里插入图片描述

2. 补充:数据结构 Hash表(哈希表)

3. Map集合

map没有实现collection接口,提供的是Key到Value的映射,一个Key只能映射一个Value,map集合中允许存储null,且可以多个。
Map接口常用方法如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
案例:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
 
public class HashMapTest {
 
    public static void main(String[] args)
    {
        Map<String,String> stuMap=new HashMap<String, String>();
        stuMap.put("001","张阿三");
        stuMap.put("003","王阿五");
        stuMap.put("002","李阿四");
 
        System.out.println("key为001的值:"+stuMap.get("001"));
 
        if(stuMap.containsKey("001"))
        {
            System.out.println("存在key为001的元素");
        }
        else
        {
            System.out.println("不存在key为001的元素");
        }
 
        if(stuMap.containsValue("李阿四"))
        {
            System.out.println("存在Value为李阿四的元素");
        }
        else
        {
            System.out.println("不存在Value为李阿四的元素");
        }
 
        System.out.println("所有Key的集合");
        Iterator<String> it=stuMap.keySet().iterator();
        while (it.hasNext())
        {
            System.out.println(it.next());
        }
 
        System.out.println("所有Value的集合");
        Iterator<String> it1=stuMap.values().iterator();
        while (it1.hasNext())
        {
            System.out.println(it1.next());
        }
 
 
        TreeMap<String,String> treeMap=new TreeMap<String, String>();
        treeMap.putAll(stuMap);
        Iterator<String> it3=treeMap.keySet().iterator();
 
        System.out.println("TreeMap类实现的集合,有序");
        while (it3.hasNext())
        {
            System.out.println(it3.next());
        }
 
    }
}

运行结果:
在这里插入图片描述

七、集合类问题

1. Array和ArrayList有何区别?

Array可以容纳基本类型和对象,而ArrayList只能容纳对象
Array是指定大小的,而ArrayList大小是固定的

2. ArrayList和LinkedList的区别是什么?

  1. ArrayList是基于数组实现,LinkedList是基于链表实现
  2. ArrayList在查找时速度快,LinkedList在插入与删除时更具优势

3. 哪些集合类是线程安全的?

常识:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行 访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染;线程不安全就是不提供数据访问保护,多线程先后更改数据会产生数据不一致或者数据污染的情况。
一般使用synchronized关键字加锁同步控制,来解决线程不安全问题。

ArrayList线程不安全,Vector线程安全;
HashMap线程不安全,HashTable线程安全;
StringBuilder线程不安全,StringBuffer线程安全
Stack也是线程安全的,继承于Vector
ConcurrentHashMap是一种高效但是线程安全的集合。

4. 线程安全和速度之间的取舍?

线程安全必须要使用synchronized关键字来同步控制,所以会导致性能的降低。
当不需要线程安全时,可以选择ArrayList,避免方法同步产生的开销;当多个线程操作同一个对象时,可以选择线程安全的Vector;

5. List、Set和Map的区别和联系?

  1. List和Set继承自Collection接口,而Map不是。Map是一组key-value映射的集合。
  2. List特点:元素有放入顺序,元素可重复
    Map特点:元素按键值对存储,无放入顺序
    Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
    参考资料:list、set和map 的区别

6. HashMap的底层结构/工作原理是什么?

HashMap内部是通过一个数组实现的,只是这个数组比较特殊,数组里存储的元素是一个Entry实体(jdk8为Node),这个Entry实体主要包含key、value以及一个指向自身的next指针。
HashMap是基于hashing实现的,当我们进行put操作时,根据传递的key值得到它的hashcode,然后再用这个hashcode与数组的长度进行模运算,得到一个int值,就是Entry要存储在数组的位置(下标);当通过get方法获取指定key的值时,会根据这个key算出它的hash值(数组下标),根据这个hash值获取数组下标对应的Entry,然后判断Entry里的key,hash值或者通过equals()比较是否与要查找的相同,如果相同,返回value,否则的话,遍历该链表(有可能就只有一个Entry,此时直接返回null),直到找到为止,否则返回null。
HashMap之所以在每个数组元素存储的是一个链表,是为了解决hash冲突问题,当两个对象的hash值相等时,那么一个位置肯定是放不下两个值的,于是hashmap采用链表来解决这种冲突,hash值相等的两个元素会形成一个链表。

7. HashMap和Hashtable的区别?

HashMap和Hashtable都实现了Map接口,HashMap几乎可以等价于Hashtable,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization)以及速度。

  1. HashMap的key和value都允许为null,而Hashtable的key和value都不允许为null。HashMap遇到key为null的时候,调用putForNullKey方法进行处理,而对value没有处理;Hashtable遇到null,直接返回NullPointerException。

  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。

  3. HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。

由于Hashtable是synchronized即线程安全的,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。

Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。

8. 哪些集合类提供对元素的随机访问?

ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。

八、java基础之抽象类和接口

  • 抽象类和接口之间的区别?
  • 什么时候创建抽象类?什么时候创建接口?
  • 设计框架时该如何选择?

1. 抽象类

抽象方法 使用 abstract 关键字修饰,仅有声明没有方法体的方法。
public abstract void f(); //没有内容
抽象类 使用 abstract 关键字修饰,即包含抽象方法的类。
如果一个类包含一个或者多个抽象方法,该类必须被限定为抽象的。抽象类可以不包含抽象方法。
接口 是抽象类的一种特殊形式,使用 interface 修饰。

public abstract class BaseActivity {
    private final String TAG = this.getClass().getSimpleName(); //抽象类可以有成员

    void log(String msg){   //抽象类可以有具体方法
        System.out.println(msg);
    }

//    abstract void initView();     //抽象类也可以没有抽象方法
}
  1. 从某种意义上来说,抽象方法就是被用来重写的,所以在父类声明的抽象方法一定要在子类里面重写。如果真的不想在子类里面重写这个方法,那么可以再在子类里面把这个方法再定义为抽象方法,因为子类觉得我去实现也不合适,应该让继承我的子类去实现比较合适,因此也可以在继承这个子类的下一个子类里面重写在父类里面声明的抽象方法,这是可以的。

  2. 这里有一个规则:既然父类里面的方法是抽象的,那么对于整个类来说,它就有一个没有实现的方法,这个方法不知道怎么去实现,那么这个类是就是残缺不全的,因此这个类应该被定义为一个抽象类。所以前面这样声明的class Animal应该要在class的前面加上abstract,即声明成这样:abstract class Animal,这样Animal类就成了一个抽象类了。

    抽象类的初衷是“抽象”,即规定这个类“是什么”,具体的实现暂不确定,是不完整的,因此不允许直接创建实例。

  • 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
  • 抽象类不能直接实例化,需要依靠子类采用向上转型的方式处理;
  • 抽象类必须有子类,使用extends继承,一个子类只能继承一个抽象类(单继承);
  • 子类(如果不是抽象类)则必须覆写抽象类之中的全部抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。);
  • 抽象类里会存在一些属性,那么抽象类中一定存在构造方法,其存在目的是为了属性的初始化。
  • 并且子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
  • 抽象类不可以用final声明, 因为抽象类必须有子类,而final定义的类不能有子类;
  • 可以直接调用抽象类中用static声明的方法, 因为任何时候,如果要执行类中的static方法的时候,都可以在没有对象的情况下直接调用,对于抽象类也一样。
    抽象类的作用:捕捉子类的通用特性

2. 接口

什么是接口(Interface)

  1. 直观上看,由interface修饰的类,就是一个接口,其声明方式为
public interface OnClickListener {
    void onClick(View v);
}
  1. 特征上来看,接口是一种特殊的抽象类,其中只包含抽象方法和全局常量,连构造方法也没有:

其成员变量,默认由public static final修饰的(可省略),且必须显式初始化,所以均为常量,最好使用大写方式表示(名称过长使用“_”分隔);
其成员方法,默认由public abstract修饰的(可省略),且不能有方法体,只是一种声明。

接口(interface),在 java 中接口属于一种引用类型,是一个抽象方法的集合。它以 interface 来声明。一个类通过重写接口中的方法来实现一个接口。接口的主要作用是达到一种类似于行为统一的“协议”。接口内的方法、返回类型等它们默认都是 static 和 final 的。它只是提供了一种形式,并没有提供具体的实现。实现一个接口必须实现它的所有抽象方法。

interface A{
	int var;							//错,必须显式初始化
	int COUNT = 1;				//合法,默认为public static final的
	public static final int CON = 2;//合法,显式声明为public static final的
	public abstract void testfun();//合法,显式声明为public abstract
	void testfun01();//合法,默认为public abstract的
	protected void testfun02();//错,接口中的方法的修饰符只能存在public、abstract、 default、 static	
	void testfun03() {};//错,接口中只能有抽象方法,不能有方法体
	static void testfun04() {}//合法,由static修饰的方法,必须有方法体	
}

Java 为了保证数据安全性是不能多继承的,也就是一个类只有一个父类。但是接口不同,一个类可以同时实现多个接口多实现),不管这些接口之间有没有关系,所以接口弥补了抽象类不能多继承的缺陷。

接口是抽象类的延伸,它可以定义没有方法体的方法,要求实现者去实现。

  • 接口的所有方法访问权限自动被声明为 public
  • 接口中可以定义“成员变量”,会自动变为 public static final 修饰的静态常量
    * 可以通过类命名直接访问:ImplementClass.name
    * 不推荐使用接口创建常量类
  • 实现接口的非抽象类必须实现接口中所有方法,抽象类可以不用全部实现
     接口不能创建对象,但可以申明一个接口变量,方便调用
     完全解耦,可以编写可复用性更好的代码

1) 接口是用来干什么的

接口是一种规范,接口定义了多个对象的共同行为,实现接口的对象可以分别实现自己的方法体。

2) 如何使用接口:

  1. 接口是不能被实例化的,所以必须有子类来实现接口,关键字为implements,子类可以同时实现多个接口,间接地实现了多继承;
  2. 如果实现接口的子类不是抽象类,那么子类中必须重写接口中所有的抽象方法;
  3. 虽然,接口不能使用new的方式进行实例化,但是可以定义接口类型的引用变量,并且使用子类对象的向上类型转换进行实例化。
public class interface01 implements A,B{
    //子类重写了接口的所有抽象方法
	@Override
	public void testfun() {
		System.out.println("接口A");
		
	}
	@Override
	public void testfun01() {
		System.out.println("接口B");
	}
	public static void main(String[] args) {
		interface01 i1 = new interface01();
		//定义接口类型的引用
		A a = i1;
		B b = i1;
		a.testfun();
		b.testfun01();
	}
}
interface A{
	public abstract void testfun();//合法,显式声明为public abstract
}
interface B{
	void testfun01();
}

3) 一个接口不能实现其他接口,但是可以继承其他多个接口

interface C extends A,B{
}

4) Instanceof

接口使用instanceof关键字,
判断一个对象是否实现了某接口。

5) 接口的好处

(1)接口反映了对象以及对对象操作的本质。
(2)代码复用,同一套代码可以处理多种不同类型的对象,只要这些对象都有相同的能力。
(3)降低了耦合,提高了灵活性。使用接口的代码依赖的是接口本身,而非实现接口的具体类型,程序可以根据情况替换接口的实现,而不影响接口使用者。

6) 可以定义方法体

在java8中。

public default void  methodDefault(){
   System.out.println();
}

5. 区别

  1. 接口只能包含抽象方法,抽象类可以包含普通方法。
  2. 接口只能定义静态常量属性,并且显式初始化;抽象类既可以定义普通属性,也可以定义静态常量属性。
  3. 接口不包含构造方法,抽象类里可以包含构造方法。抽象类不能被实例化,但不代表它不可以有构造函数,抽象类可以有构造函数,备继承类扩充
  4. 抽象类是一个特殊的类,但是接口与类是不同的类型
  5. 接口是核心,其定义了要做的事情,包含了许多的方法,但没有定义这些方法应该如何做。
  6. 如果许多类实现了某个接口,那么每个都要用代码实现那些方法
  7. 如果某一些类的实现有共通之处,则可以抽象出来一个抽象类,让抽象类实现接口的公用的代码,而那些个性化的方法则由各个子类去实现。
  8. 抽象类是为了简化接口的实现,他不仅提供了公共方法的实现,让你可以快速开发,又允许你的类完全可以自己实现所有的方法,不会出现紧耦合的问题。

应用场合很简单了

  1. 优先定义接口
  2. 如果有多个接口实现有公用的部分,则使用抽象类,然后集成它。

十、Java基础之线程

1. 线程的四种状态

新建(new)、就绪(Runnable)、阻塞(Bolocked)、死亡(Dead)。

2. 什么是线程?它与进程有什么区别?

线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在 Java 语言中线程有 4 种运行状态:运行、就绪、挂起和结束。
进程是指一段正在执行的程序。而线程有时也被称为轻量级的进程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源(例如打开文件),但是各个线程拥有自己的栈空间

3. 多线程与单线程的区别

生活举例
你早上上班,正要打卡的时候,手机响了。。你如果先接了电话,等接完了,在打卡,就是单线程。
如果你一手接电话,一手打卡。就是多线程。
2件事的结果是一样的。。你接了电话且打了卡。

4. 如何实现 Java 的多线程

1) 继承 Thread 类,重写 run() 方法

Thread 本质上也是实现了 Runnable 接口的一个实例,它代表了一个线程的实例,并且启动线程的唯一方法,就是通过 Thread 类的 start() 的方法,start() 方法是一个 native (本地)方法,它将启动一个新线程,并执行 run 方法,(Thread 中提供的 run() 方法是一个方法),这种方式通过自定义直接 extend Thread,并重写 run() 方法,就可以启动新线程并执行自己定义的额 run() 方法,需要注意的是,调用 start() 方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行多线程代码是由操作系统决定的
在这里插入图片描述
那么如何启动这个线程呢?只需要 new 出 MyThread 的实例,然后调用它的 start() 方法,这样 run() 方法中的代码就会运行在子线程,
如下:new MyThread().start();

2) 实现 Runnable 接口,并实现该接口的 run() 方法

在这里插入图片描述
如果使用这种写法,启动线程的方法也需要相应的改变,如下:
在这里插入图片描述
Thread 构造函数接受一个 Runnable 参数,我们 new 出的 MyThread 正是一个实现了 Runnable 接口的对象,所以可以直接将它传入到 Thread 的构造函数里,接着就和上面的一样了,调用 Thread 的 start() 方法,run() 方法中的代码就会在子线程当中运行了

其实,不管是通过继承 Thread 类还是通过使用 Runnable 接口来实现多线成的方法,最终还是通过 Thread 的对象的 API 来控制线程的。

3) 实现 Callable 接口,重写 call 方法

Callable 接口实际是属于 Executor 框架中的功能类,Callable 接口与 Runnable 接口的功能类似,但提供了比 Runnable 更强大的功能,主要表现为以下 3 点:

  • Callable 可以在任务结束后提供一个返回值,Runnable 无法提供这个功能

  • Callable 中的 call 方法可以抛出异常,而 Runnable 的 run 方法不能抛出异常

  • 运行 Callable 可以拿到一个 Future 对象,Future 对象表示异步计算的结果,它提供了检查计算是否完成的方法,由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用 Future 来监视目标线程调用 call() 方法的情况,当调用 Future 的 get() 方法以获取结果时,当前线程就会阻塞以,直到 call() 方法结束返回结果
    在这里插入图片描述
    以上代码只供参考,以上 3 种方式中,前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的,当需要实现多线程时,一般推荐实现 Runnable 接口的方式,其原因是:
    首先,Thread 类定义了多种方法可以被派生类使用或重写,但是只有 run() 方法是必须被重写的,在 run() 方法中实现这个线程的主要功能,这当然是实现 Runnable 接口所需的方法

    其次,很多 Java 开发人员认为,一个类仅在需要被加强或修改时,才会被继承,因此如果没有必要重写 Thread 类中的其它方法,那么继承 Thread 的实现方式与实现 Runnable 接口的效果相同,在这种情况下最好通过实现 Runnable 接口的方式来创建线程

引申:一个类是否可以同时继承 Thread 与实现 Runnable 接口?

首先说明是可以的,为了说明这个问题,给出如下示例:

5. run() 方法与 start() 方法有什么区别?

通常,系统通过调用线程类的 start() 方法启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以被 JVM 来调度执行,在调度过程中,JVM 通过调用线程类的 run() 方法来完成实际的操作,当 run() 方法结束后,线程就会终止

如果直接调用线程类的 run() 方法,这会被当作一个普通的函数,程序中仍然只有主线程这一个线程,也就是说 start() 方法能够异步的调用 run() 方法,但是直接调用 run() 方法却是同步的,因此也就无法达到多线程的目的,由此可见,只有通过调用线程类的 start() 方法才能达到多线程的目的

6. 多线程同步实现的方法有哪些?

1) synchronized 关键字

在 Java 语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只容许被一个对象所拥有,当一个线程调用对象的一段 synchronized 代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后释放锁
synchronized 关键字**主要有两种用法(synchronized 方法和 synchronized 块)**,此外该关键字还可以作用于静态方法,或某个实例,但这都对程序的效率有一定的影响

  • synchronized 方法:在方法的声明前加入 synchronized 关键字,示例如下
    在这里插入图片描述
    只要把多个线程对类需要被同步的资源的操作放到 Test() 方法中,就能保证这个方法在同一时刻只能被一个线程访问,从而保证了多线程访问的安全,然而当一个线程的方法体规模非常大时,把该方法声明为 synchronized 会大大影响程序的执行效率,为了提高程序的效率,Java 提供了 synchronized 块
  • synchronized 块:synchronized 块既可以把任意的代码段声明为 synchronized,也可以指定上锁的对象,有非常高的灵活性,其用法如下:
    在这里插入图片描述

2) wait() 方法与 notify() 方法

当使用某个 synchronized 来修饰某个共享资源时,如果线程 A1 在执行 synchronized 代码,另外一个线程 A2 也将要同时执行同一对象的同一个 synchronized 代码时,线程 A2 将要等到线程 A1 执行完成后,才能继续执行,在这种情况下可以使用 notify() 方法和 wait() 方法
在 synchronized 代码被执行期间,线程可以调用对象的 wait() 方法,释放对象锁,进入等待状态,并且可以调用 notify() 方法或 notifyAll() 方法通知正在等待的其他线程,notify() 方法仅唤醒一个线程(等待队列中的第一个线程),并容许它去获得锁,notifyAll() 方法唤醒所有等待这个对象的线程,并允许他们去获得锁(并不是让所有唤醒线程都获得锁,而是让他们去竞争)。

3) Lock

lock(): 以阻塞方式获取锁,也就是说如果获取到了锁,立即返回,如果别的线程持有锁,当前线程等待,直到获取锁后返回。

tryLock(): 以非阻塞方式获取锁,只是尝试性的去获取一下锁,如果获取到锁,立即返回 true,否则返回 false。

7. sleep() 方法与 wait() 方法有什么区别?

sleep() 方法与 wait() 方法都是使线程暂停执行一段时间的方法,具体而言,sleep() 方法与 wait() 方法的区别主要表现在如下几个方面:
sleep() 方法是 Thread 类的静态方法,是线程用来控制自身流程的,它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,次线程会自动苏醒,而 wait() 方法是 Object 类的方法,用于线程间的通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用 notify() 方法(或 notifyAll() 方法)时才醒来,不过开发人员也可以给他指定一个时间,自动醒来

2) 对象锁处理机制不同

由于 sleep() 方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此调用 sleep() 方法并不会释放锁,而 wait() 方法则不同,当调用 wait() 方法后,线程会释放掉它所占用的锁,从而使线程所在对象中的其他synchronized 数据可被别的线程使用

3) 使用区域不同

由于 wait() 方法的特殊意义,因此它必须放在同步控制方法或者同步语句块中使用,而 sleep() 方法可以在任何地方使用
最后由于 sleep() 方法不会释放锁标志,容易导致死锁问题的发生,因此一般情况下不推荐使用,而推荐使用 wait() 方法

8. 终止线程的方法有哪些?

在 Java 语言中,可以使用 stop() 方法与 suspend() 方法来终止线程的执行,当用 Thread.stop() 来终止线程时,它会释放已经锁定的所有监视资源,调用 suspend() 方法容易发生死锁(死锁指的是两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果无外力作用,它们都将无法推进),由于调用 suspend() 方法不会释放锁,这就会导致一个问题:如果用一个 suspend 挂起一个有锁的线程,那么在锁恢复之前将不会被释放,如果调用 suspend() 方法,线程将试图取得相同的锁,程序就会发生死锁

9. 什么是守护线程?

Java 提供了两种线程:守护线程与用户线程,守护线程又被称为“服务进程”或“后台线程”是指在程序运行时,在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分,通俗点将,任何一个守护线程都是整个 JVM 中所有非守护线程的“保姆”
用户线程和守护线程几乎一样,唯一的不同之处就在于如果用户线程已经全部退出运行,只剩下守护线程存在,JVM 也就退出了,因为当所有非守护程序结束时,没有了被守护者,守护线程也就没有工作可做了,也就没有运行程序的必要,程序也就终止了,同时会杀死所有守护线程

1) 原理不同

十一、java8之lamda表达式

Lambda表达式可以由编译器推断并转换包装为常规的代码,因此可以使用更少的代码来实现同样的功能。
Java中使用Lambda表达式的前提是:必须有“函数式接口”
线程中的Runnable也是一个函数式接口,其中的 run 方法就是一个函数式接口
在这里插入图片描述

1. 函数式接口

有且仅有一个抽象方法的接口,叫做函数式接口。
注意:

  • 是接口
  • 有且仅有一个抽象方法:不能没有,不能有2个及以上,同时可以有默认方法、静态方法等。
    如何万无一失地检测当前接口是不是函数式接口呢? 用一个固定的格式写在public interface之前一行即可:
    在这里插入图片描述
    如下面代码定义的接口就是函数式接口:只有一个抽象方法。
    在这里插入图片描述

2. Lambda表达式标准接口

Lambda表达式要想使用,一定要有函数式接口的推断环境

  1. 要么通过方法的参数类型来确定是哪个函数式接口;
  2. 要么通过赋值操作来确定是哪个函数式接口;(等号左侧的对象类型)

Lambda的格式就是为了将抽象方法的头,翻译为以下3点:

  1. 一些参数:方法参数
  2. 一个箭头:->
  3. 一些代码:方法体
    例如抽象方法:public abstract int sum(int x,int z);
    翻译成为Lambda的标准格式:
    在这里插入图片描述

省略格式:在Lambda表达式中,凡是可以推导的,都可以省略

  1. Lambda表达式中的参数类型可以省略不写;
  2. 如果参数有且只有一个,那么小括号可以省略不写;
  3. 如果语句只有一个,那么大括号和return语句也可以省略;

3. 例子:

定义一个函数式接口Calculator
在这里插入图片描述
在这里插入图片描述
运行结果:
在这里插入图片描述

4. Lambda表达式的冗余

来看一个例子:
首先定义一个类PrintClass,这个类有一个静态方法print,用于打印给定的字符串:
在这里插入图片描述
然后我定义了一个函数式接口PrintMsg
在这里插入图片描述
在main函数里我用Lambda表达式指定了接口的作用:
在这里插入图片描述
运行结果如图:
在这里插入图片描述
是我们想要的结果。但是我们发现,在我们定义的类PrintClass里的静态方法printStr已经实现了这个功能,但是在main里又通过Lambda表达式重新写了一遍,这没有必要,能不能通过Lambda表达式直接调用已有的方法呢?

5. Lambda引用类的静态方法和成员方法

上面的问题的答案是肯定的。这即是通过引用类的方法来实现。分为两种,静态方法和成员方法。
对于静态方法,通过 类名::静态方法名;
对于成员方方法,通过 类的对象名::成员方法名;

还是上面的例子:
在这里插入图片描述

6. 自己观后感

这个lamda表达式 就是对接口 填上了方法体。 进行调用。并没有传参,直接通过方法进行传参。

自定义函数式接口,在第十二章中的最后。

十二、Java之外部类、内部类

1. 外部类

外部类,顾名思义,就是外部的类。定义一个类A,在A的内部再定义一个类B,则A就是外部了类,B就是内部类

2. 内部类

内部类一般来说共分为4种:成员(常规)内部类、静态内部类、局部内部类、匿名内部类。
或者说分为 静态内部类和非静态内部类(成员(常规)内部类、局部内部类、匿名内部类)。

// 静态内部类
public class School {   
	private static School instance = null;  
	static class Teacher {} 
}

// 非静态内部类:成员内部类
public class School {   private String name;   
	class Teacher {} 
}

1) 静态内部类和非静态内部类之间的区别主要如下:

内部原理的区别:

  • 静态内部类: 是属于外部类的类成员,是一种静态的成员,是属于类的,就有点类似于
    private static Singleton instance = null;
  • 非静态内部类: 是属于外部类的实例对象的一个实例成员,也就是说,每个非静态内部类,不是属于外部类的,是属于外部类的每一个实例的,创建非静态内部类的实例以后,非静态内部类实例,是必须跟一个外部类的实例进行关联和有寄存关系的。

2、创建方式的区别:

  • 创建静态内部类的实例的时候,只要直接使用 “外部类.内部类()” 的方式,就可以,比如
new School.Teacher()
  • 创建非静态内部类的实例的时候,必须要先创建一个外部类的实例,然后通过外部类的实例,再来创建内部类的实例,
School s = new School;
s.Teacher t = s.new Teacher()
  • 通常来说,我们一般都会为了方便,会选择使用静态内部类。

2) 静态内部类

  1. 静态内部类可以等同看做静态变量
  2. 内部类重要的作用:可以访问外部类中的私有数据
  3. 静态内部类可以直接访问外部类的静态数据,无法直接访问成员变量和成员方法
public class School {   
	private static School instance = null;  
	static class Teacher {} 
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3) 成员内部类

  1. 成员内部类可以等同看做成员变量
  2. 成员内部类 不能 有静态声明(静态方法,静态变量)
  3. 成员内部类可以访问外部的所有方法,变量
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

4) 局部内部类(成员方法中的类)

局部内部类等同于局部变量
重点:局部内部类在访问局部变量的时候,局部变量必须使用final修饰

5) 匿名内部类

指的是类没有名字。
在这里插入图片描述
使用匿名内部类实现:少定义一个类(实现的那个类)
在这里插入图片描述

6) 典型的匿名内部类的使用并和lamda表示式关系

a) 例子1:无参函数的简写

如果需要新建一个线程,一种常见的写法是这样:
在这里插入图片描述
上述代码给Tread类传递了一个匿名的Runnable对象,重载Runnable接口的run()方法来实现相应逻辑。这是JDK7以及之前的常见写法。匿名内部类省去了为类起名字的烦恼,但还是不够简化,在Java 8中可以简化为如下形式:
在这里插入图片描述
上述代码跟匿名内部类的作用是一样的,但比匿名内部类更进一步。这里连接口名和函数名都一同省掉了,写起来更加神清气爽。

b) 例子2:带参函数的简写

如果要给一个字符串列表通过自定义比较器,按照字符串长度进行排序,Java 7的书写形式如下:

上述代码通过内部类重载了Comparator接口的compare()方法,实现比较逻辑。采用Lambda表达式可简写如下:
在这里插入图片描述
上述代码跟匿名内部类的作用是一样的。除了省略了接口名和方法名,代码中把参数表的类型也省略了。这得益于javac的类型推断机制,编译器能够根据上下文信息推断出参数的类型,当然也有推断失败的时候,这时就需要手动指明参数类型了。注意,Java是强类型语言,每个变量和对象都必需有明确的类型。

c) 简写的依据

也许你已经想到了,能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口)。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。Lambda表达更多合法的书写形式如下:
在这里插入图片描述
上述代码中,1展示了无参函数的简写;2处展示了有参函数的简写,以及类型推断机制;3是代码块的写法;4和5再次展示了类型推断机制。

d) 自定义函数接口

自定义函数接口很容易,只需要编写一个只有一个抽象方法的接口即可。
在这里插入图片描述
上面代码中的**@FunctionalInterface是可选的,但加上该标注编译器会帮你检查接口是否符合函数接口规范**。就像加入@Override标注会检查是否重载了函数一样。
有了上述接口定义,就可以写出类似如下的代码:
在这里插入图片描述
进一步的,还可以这样使用:
在这里插入图片描述

3. 内部类与外部类的联系

十三、函数式接口的三种实现方式

目录:
在这里插入图片描述
函数式接口:
在这里插入图片描述

1. 第一种:实现接口方式

写一个实现类 DaoImpl
在这里插入图片描述
运行:
在这里插入图片描述

2. 第二种:匿名内部类

在这里插入图片描述

3. 第三种:lamada表达式

在这里插入图片描述
十二、java之设计模式

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页