Java基础(总结)

Java基础知识总结

写代码:

    1,明确需求。我要做什么?

    2,分析思路。我要怎么做?1,2,3。

    3,确定步骤。每一个思路部分用到哪些语句,方法,和对象。

    4,代码实现。用具体的java语言代码把思路体现出来。

  学习新技术的四点:

    1,该技术是什么?

    2,该技术有什么特点(使用注意):

    3,该技术怎么使用。demo

    4,该技术什么时候用?test。

——————————————————————————————————————————————————————

一:java概述:

  1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒、PDA等的微处理器;

  1994年将Oak语言更名为Java;

  Java的三种技术架构:

  JAVAEE:Java Platform Enterprise Edition,开发企业环境下的应用程序,主要针对web程序开发;

  JAVASE:Java Platform Standard Edition,完成桌面应用程序的开发,是其它两者的基础;

  JAVAME:Java Platform Micro Edition,开发电子消费产品和嵌入式设备,如手机中的程序;

  1,JDK:Java Development Kit:是Java程序开发工具包,包含 JRE 和开发人员使用的工具。 我们想要运行一个已有的Java程序,那么只需安装 JRE 即可。 我们想要开发一个全新的Java程序,那么必须安装 JDK ,其内部包含 JRE 。

  2,JRE:Java Runtime Environment:是Java程序的运行时环境,包含 JVM 和运行时所需要的 核心类库 。

  3,配置环境变量:

  4,javac命令和java命令做什么事情呢?

    要知道java是分两部分的:一个是编译,一个是运行。

javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。 生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。

  java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数.

——————————————————————————————————————————————————————

二:java语法基础:

1,关键字:其实就是某种语言赋予了特殊含义的单词。

  保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词。

2,标示符:其实就是在程序中自定义的名词。比如类名,变量名,函数名。包含 0—9、a—z、$、_ ;

  注意:

    ①:数字不可以开头。

    ②:不可以使用关键字。

3,常量:是在程序中的不会变化的数据。

4,变量:其实就是内存中的一个存储空间,用于存储常量数据。

  作用:方便于运算。因为有些数据不确定。所以确定该数据的名词和存储空间。

  特点:变量空间可以重复使用。

  什么时候定义变量?只要是数据不确定的时候,就定义变量。

  变量空间的开辟需要什么要素呢?

    ①:这个空间要存储什么数据?数据类型。

    ②:这个空间叫什么名字啊?变量名称。

    ③:这个空间的第一次的数据是什么? 变量的初始化值。

  变量的作用域和生存期:

    变量的作用域:

    作用域从变量定义的位置开始,到该变量所在的那对大括号结束;

    生命周期:

    变量从定义的位置开始就在内存中活了;

    变量到达它所在的作用域的时候就在内存中消失了;

  数据类型:

    ①:基本数据类型:byte、short、int、long、float、double、char、boolean

    ②:引用数据类型: 数组、类、接口。

    级别从低到高为:byte,char,short(这三个平级)——>int——>float——>long——>double

    自动类型转换:从低级别到高级别,系统自动转的;

    强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量;

  运算符号:

    ①、算术运算符。

      + — * / %

      %:任何整数模2不是0就是1,所以只要改变被模数就可以实现开关运算。

      +:连接符。

      ++,——

    ②、赋值运算符。

      = += —= *= /= %=

    ③、比较运算符。

      特点:该运算符的特点是:运算完的结果,要么是true,要么是false。

    ④、逻辑运算符。

      & | ^ ! && ||

      逻辑运算符除了 ! 外都是用于连接两个boolean类型表达式。

      &: 只有两边都为true结果是true。否则就是false。

      |:只要两边都为false结果是false,否则就是true

      ^:异或:和或有点不一样。

        两边结果一样,就为false。

        两边结果不一样,就为true.

      & 和 &&区别: & :无论左边结果是什么,右边都参与运算。

             &&:短路与,如果左边为false,那么右边不参数与运算。

      | 和|| 区别: |:两边都运算。

             ||:短路或,如果左边为true,那么右边不参与运算。

    ⑤、位运算符:用于操作二进制位的运算符。

      & | ^

      << >> >>>(无符号右移)

    练习:对两个变量的数据进行互换。不需要第三方变量。

    int a = 3,b = 5;//——>b = 3,a = 5;

    a = a + b; a = 8;

    b = a — b; b = 3;

    a = a — b; a = 5;

    a = a ^ b;//

    b = a ^ b;//b = a ^ b ^ b = a

    a = a ^ b;//a = a ^ b ^ a = b;

    练习:高效的算出 2*8 = 2<<3;

5,语句。

  If    switch   do while    while    for

  这些语句什么时候用?

  1)、当判断固定个数的值的时候,可以使用if,也可以使用switch。

    但是建议使用switch,效率相对较高。

    switch(变量){

      case 值:要执行的语句;break;

      …

      default:要执行的语句;

    }

    工作原理:用小括号中的变量的值依次和case后面的值进行对比,和哪个case后面的值相同了

         就执行哪个case后面的语句,如果没有相同的则执行default后面的语句;

    细节:①:break是可以省略的,如果省略了就一直执行到遇到break为止;

       ②、switch 后面的小括号中的变量应该是byte,char,short,int四种类型中的一种;

       ③、default可以写在switch结构中的任意位置;如果将default语句放在了第一行,

        则不管expression与case中的value是否匹配,程序会从default开始执行直到第一个break出现。

  2)、当判断数据范围,获取判断运算结果boolean类型时,需要使用if。

  3)、当某些语句需要执行很多次时,就用循环结构。

  while和for可以进行互换。

    区别在于:如果需要定义变量控制循环次数。建议使用for。因为for循环完毕,变量在内存中释放。

  break:作用于switch ,和循环语句,用于跳出,或者称为结束。

  break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。

  continue:只作用于循环结构,继续循环用的。

  作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。

6,函 数:为了提高代码的复用性,可以将其定义成一个单独的功能,该功能的体现就是java中的函数。函数就是体现之一。

  java中的函数的定义格式:

  修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数1,…){

    执行语句;

    return 返回值;

  }

  当函数没有具体的返回值时,返回的返回值类型用void关键字表示。

  如果函数的返回值类型是void时,return语句可以省略不写的,系统会帮你自动加上。

  return的作用:结束函数。结束功能。

  如何定义一个函数?

    函数其实就是一个功能,定义函数就是实现功能,通过两个明确来完成:

      ①、明确该功能的运算完的结果,其实是在明确这个函数的返回值类型。

      ②、在实现该功能的过程中是否有未知内容参与了运算,其实就是在明确这个函数的参数列表(参数类型&参数个数)。

  函数的作用:

    1)、用于定义功能。

    2)、用于封装代码提高代码的复用性。

    注意:函数中只能调用函数,不能定义函数。

  主函数:

    1)、保证该类的独立运行。

    2)、因为它是程序的入口。

    3)、因为它在被jvm调用。

  函数定义名称是为什么呢?

    答:1)、为了对该功能进行标示,方便于调用。

      2)、为了通过名称就可以明确函数的功能,为了增加代码的阅读性。

  重载的定义是:在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。

  如何区分重载:当函数同名时,只看参数列表。和返回值类型没关系。

7,数 组:用于存储同一类型数据的一个容器。

  好处:可以对该容器中的数据进行编号,从0开始。数组用于封装数据,就是一个具体的实体。

  如何在java中表现一个数组呢?两种表现形式。

    1)、元素类型[] 变量名 = new 元素类型[元素的个数];

    2)、元素类型[] 变量名 = {元素1,元素2...};

      元素类型[] 变量名 = new 元素类型[]{元素1,元素2...};

——————————————————————————————————————————————————————

    //二分查找法。必须有前提:数组中的元素要有序。

    

——————————————————————————————————————————————————————

java分了5片内存。

  1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。

  栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );

    只要数据运算完成所在的区域结束,该数据就会被释放。

  堆:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。

    1:每一个实体都有内存首地址值。

    2:堆内存中的变量都有默认初始化值。因为数据类型不同,值也不一样。

    3:垃圾回收机制。

——————————————————————————————————————————————————————

三:面向对象:★★★★★

  特点:1:将复杂的事情简单化。

     2:面向对象将以前的过程中的执行者,变成了指挥者。

     3:面向对象这种思想是符合现在人们思考习惯的一种思想。

  过程和对象在我们的程序中是如何体现的呢?

    过程其实就是函数;

    对象是将函数等一些内容进行了封装。

  匿名对象使用场景:

    1:当对方法只进行一次调用的时候,可以使用匿名对象。

    2:当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。

  在类中定义其实都称之为成员。成员有两种:

    1:成员变量:其实对应的就是事物的属性。

    2:成员函数:其实对应的就是事物的行为。

  所以,其实定义类,就是在定义成员变量和成员函数。但是在定义前,必须先要对事物进行属性和行为的分析,才可以用代码来体现。

  private int age;//私有的访问权限最低,只有在本类中的访问有效。

  注意:私有仅仅是封装的一种体现形式而已。

  私有的成员:其他类不能直接创建对象访问,所以只有通过本类对外提供具体的访问方式来完成对私有的访问,

         可以通过对外提供函数的形式对其进行访问。

  好处:可以在函数中加入逻辑判断等操作,对数据进行判断等操作。

  总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,所以,类中的属性通常被私有化,并对外提供公共的访问方法。

这个方法一般有两个,规范写法:对于属性 xxx,可以使用setXXX(),getXXX()对其进行操作。

  类中怎么没有定义主函数呢?

    注意:主函数的存在,仅为该类是否需要独立运行,如果不需要,主函数是不用定义的。

    主函数的解释:保证所在类的独立运行,是程序的入口,被jvm调用。

  成员变量和局部变量的区别:

    1:成员变量直接定义在类中。

    局部变量定义在方法中,参数上,语句中。

    2:成员变量在这个类中有效。

      局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。

    3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。

      局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。

  构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。

    特点:

      1:该函数的名称和所在类的名称相同。

      2:不需要定义返回值类型。

      3:该函数没有具体的返回值。

    记住:所有对象创建时,都需要初始化才可以使用。

    注意事项:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,

        为了方便该类创建对象,完成初始化。如果在类中自定义了构造函数,那么默认的构造函数就没有了。

    一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。所以,

    一个类中如果出现多个构造函数。它们的存在是以重载体现的。

  构造函数和一般函数有什么区别呢?

    1:两个函数定义格式不同。

    2:构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。

      一般函数,是对象创建后,需要调用才执行,可以被调用多次。

  什么时候使用构造函数呢?

    分析事物时,发现具体事物一出现,就具备了一些特征,那就将这些特征定义到构造函数内。

  构造代码块和构造函数有什么区别?

    构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。

    构造函数:是给与之对应的对象进行初始化。它具有针对性。

  Person p = new Person();

  创建一个对象都在内存中做了什么事情?

    1:先将硬盘上指定位置的Person.class文件加载进内存。

    2:执行main方法时,在栈内存中开辟了main方法的空间(压栈—进栈),然后在main方法的栈区分配了一个变量p。

    3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new

    4:在该实体空间中进行属性的空间分配,并进行了默认初始化。

    5:对空间中的属性进行显示初始化。

    6:进行实体的构造代码块初始化。

    7:调用该实体对应的构造函数,进行构造函数初始化。()

    8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)

——————————————————————————————————————————————————————

封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。

  好处:将变化隔离;便于使用;提高重用性;安全性。

  封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。

  this:代表对象。就是所在函数所属对象的引用。

    this到底代表什么呢?哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。

    开发时,什么时候使用this呢?

    在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。

    this 还可以用于构造函数间的调用。

    调用格式:this(实际参数);

    this对象后面跟上 . 调用的是成员属性和成员方法(一般方法);

    this对象后面跟上 () 调用的是本类中的对应参数的构造函数。

    注意:用this调用构造函数,必须定义在构造函数的第一行。

  因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。

  static:★★★ 关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。

  特点:

    1,想要实现对象中的共性数据的对象共享。可以将这个数据进行静态修饰。

    2,被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方式。

    3,静态随着类的加载而加载。而且优先于对象存在。

  弊端:

    1,有些数据是对象特有的数据,是不可以被静态修饰的。因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。

    2,静态方法只能访问静态成员,不可以访问非静态成员。

      因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。

    3,静态方法中不能使用this,super关键字。

      因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。

    4,主函数是静态的。

  什么时候定义静态成员呢?或者说:定义成员时,到底需不需要被静态修饰呢?

  成员分两种:

    1,成员变量。(数据共享时静态化)

      该成员变量的数据是否是所有对象都一样:

      如果是,那么该变量需要被静态修饰,因为是共享的数据。

      如果不是,那么就说这是对象的特有数据,要存储到对象中。

    2,成员函数。(方法中没有调用特有数据时就定义成静态)

      如果判断成员函数是否需要被静态修饰呢?

      只要参考,该函数内是否访问了对象中的特有数据:

      如果有访问特有数据,那方法不能被静态修饰。

      如果没有访问过特有数据,那么这个方法需要被静态修饰。

  成员变量和静态变量的区别:

    1,成员变量所属于对象。所以也称为实例变量。

      静态变量所属于类。所以也称为类变量。

    2,成员变量存在于堆内存中。

      静态变量存在于方法区中。

    3,成员变量随着对象创建而存在。随着对象被回收而消失。

      静态变量随着类的加载而存在。随着类的消失而消失。

    4,成员变量只能被对象所调用 。

      静态变量可以被对象调用,也可以被类名调用。

    所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

  静态的注意:静态的生命周期很长。

  静态代码块:就是一个有静态关键字标示的一个代码块区域。定义在类中。

  作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。

  Public:访问权限最大。

  static:不需要对象,直接类名即可。

  void:主函数没有返回值。

  Main:主函数特定的名称。

  (String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。

  jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。

  静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 ———> 构造代码块 ———> 构造函数;

——————————————————————————————————————————————————————

  设计模式:解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

java中有23种设计模式:

单例设计模式:★★★★★

  解决的问题:保证一个类在内存中的对象唯一性。

  比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。

  Runtime()方法就是单例设计模式进行设计的。

  如何保证对象唯一性呢?

  思想:

    1,不让其他程序创建该类对象。

    2,在本类中创建一个本类对象。

    3,对外提供方法,让其他程序获取这个对象。

  步骤:

    1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;

    2,就在类中创建一个本类的对象;

    3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控)

  代码体现:

    1,私有化构造函数;

    2,创建私有并静态的本类对象;

    3,定义公有并静态的方法,返回该对象。

——————————————————————————————————————————————————————

  饿汉式

  class Single{

    private Single(){} //私有化构造函数。

    private static Single s = new Single(); //创建私有并静态的本类对象。

    public static Single getInstance(){ //定义公有并静态的方法,返回该对象。

      return s;

    }

  }

——————————————————————————————————————————————————————

  懒汉式:延迟加载方式。

  class Single2{

    private Single2(){}

    private static Single2 s = null;

    public static Single2 getInstance(){

      if(s==null)

      s = new Single2();

      return s;

    }

  }

——————————————————————————————————————————————————————

继 承(面向对象特征之一)

  好处:

    1:提高了代码的复用性。

    2:让类与类之间产生了关系,提供了另一个特征多态的前提。

  父类的由来:其实是由多个类不断向上抽取共性内容而来的。

  java中对于继承,java只支持单继承。java虽然不直接支持多继承,但是保留了这种多继承机制,进行改良。

  单继承:一个类只能有一个父类。

  多继承:一个类可以有多个父类。

  为什么不支持多继承呢?

  因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?因为父类中的方法中存在方法体。

  但是java支持多重继承。A继承B B继承C C继承D。

  多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。

  所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。

  简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。

  子父类出现后,类中的成员都有了哪些特点:

  1:成员变量。

    当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。

    如果想要调用父类中的属性值,需要使用一个关键字:super

    This:代表是本类类型的对象引用。

    Super:代表是子类所属的父类中的内存空间引用。

    注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。

  2:成员函数。

    当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:覆盖(复写,重写)

    什么时候使用覆盖呢?当一个类的功能内容需要修改时,可以通过覆盖来实现。

  3:构造函数。

    发现子类构造函数运行时,先运行了父类的构造函数。为什么呢?

    原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super();

    super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。

    为什么子类对象初始化时,都需要调用父类中的函数?(为什么要在子类构造函数的第一行加入这个super()?)

      因为子类继承父类,会继承到父类中的数据,必须要看父类是如何对自己的数据进行初始化的。

      所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。

  注意:

  子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();

  如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。

  如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。

  问题:super()和this()是否可以同时出现的构造函数中。

  两个语句只能有一个定义在第一行,所以只能出现其中一个。

  super()或者this():为什么一定要定义在第一行?

  因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。

  继承的细节:

    什么时候使用继承呢?

    当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。

    英文书中,所属关系:" is a "

      注意:不要仅仅为了获取其他类中的已有成员进行继承。

    所以判断所属关系,可以简单看,

    如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。

  细节二:

    在方法覆盖时,注意两点:

      1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。

      2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)

  继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。

  这时如何解决问题呢?介绍一个关键字,final:最终。

  final特点:

    1:这个关键字是一个修饰符,可以修饰类,方法,变量。

    2:被final修饰的类是一个最终类,不可以被继承。

    3:被final修饰的方法是一个最终方法,不可以被覆盖。

    4:被final修饰的变量是一个常量,只能赋值一次。

  其实这样的原因的就是给一些固定的数据起个阅读性较强的名称。

  不加final修饰不是也可以使用吗?那么这个值是一个变量,是可以更改的。加了final,程序更为严谨。常量名称定义时,有规范,所有字母都大写,如果由多个单词组成,中间用 _ 连接。

抽象类: abstract

  抽象:不具体,看不明白。抽象类表象体现。

  在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法。

  抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。

  抽象类的特点:

    1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。

    2:抽象方法只定义方法声明,并不定义方法实现。

    3:抽象类不可以被创建对象(实例化)。

    4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

  抽象类的细节:

    1:抽象类中是否有构造函数?有,用于给子类对象进行初始化。

    2:抽象类中是否可以定义非抽象方法?

      可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。所以抽象类和一般类在定义上,都是需要定义属性和行为的。只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。

    3:抽象关键字abstract和哪些不可以共存?final , private , static

    4:抽象类中可不可以不定义抽象方法?可以。抽象方法目的仅仅为了不让该类创建对象。

——————————————————————————————————————————————————————

模板方法设计模式:

  解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

  abstract class GetTime{

    public final void getTime(){   //此功能如果不需要复写,可加final限定

      long start = System.currentTimeMillis();

      code();   //不确定的功能部分,提取出来,通过抽象方法实现

      long end = System.currentTimeMillis();

      System.out.println("毫秒是:"+(end—start));

    }

    public abstract void code();   //抽象不确定的功能,让子类复写实现

  }

  class SubDemo extends GetTime{

    public void code(){   //子类复写功能方法

      for(int y=0; y<1000; y++){

        System.out.println("y");

      }

    }

  }

——————————————————————————————————————————————————————

接 口:★★★★★

  1:是用关键字interface定义的。

  2:接口中包含的成员,最常见的有全局常量、抽象方法。

    注意:接口中的成员都有固定的修饰符。

    成员变量:public static final

    成员方法:public abstract

    interface Inter{

      public static final int x = 3;

      public abstract void show();

    }

  3:接口中有抽象方法,说明接口不可以实例化。接口的子类必须实现了接口中所有的抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

  4:类与类之间存在着继承关系,类与接口中间存在的是实现关系。

    继承用extends ;实现用implements ;

  5:接口和类不一样的地方,就是,接口可以被多实现,这就是多继承改良后的结果。java将多继承机制通过多现实来体现。

  6:一个类在继承另一个类的同时,还可以实现多个接口。所以接口的出现避免了单继承的局限性。还可以将类进行功能的扩展。

  7:其实java中是有多继承的。接口与接口之间存在着继承关系,接口可以多继承接口。

  接口都用于设计上,设计上的特点:(可以理解主板上提供的接口)

    1:接口是对外提供的规则。

    2:接口是功能的扩展。

    3:接口的出现降低了耦合性。

  抽象类与接口:

  抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。

  接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。

  抽象类和接口的共性:都是不断向上抽取的结果。

  抽象类和接口的区别:

    1:抽象类只能被继承,而且只能单继承。

      接口需要被实现,而且可以多实现。

    2:抽象类中可以定义非抽象方法,子类可以直接继承使用。

      接口中都有抽象方法,需要子类去实现。

    3:抽象类使用的是 is a 关系。

      接口使用的 like a 关系。

    4:抽象类的成员修饰符可以自定义。

      接口中的成员修饰符是固定的。全都是public的。

  在开发之前,先定义规则,A和B分别开发,A负责实现这个规则,B负责使用这个规则。至于A是如何对规则具体实现的,B是不需要知道的。这样这个接口的出现就降低了A和B直接耦合性。

——————————————————————————————————————————————————————

多 态★★★★★(面向对象特征之一):函数本身就具备多态性,某一种事物有不同的具体的体现。

  体现:父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat();

  多态的好处:提高了程序的扩展性。

  多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性)

  多态的前提:

    1:必须要有关系,比如继承、或者实现。

    2:通常会有覆盖操作。

  多态的出现思想上也做着变化:以前是创建对象并指挥对象做事情。有了多态以后,我们可以找到对象的共性类型,直接操作共性类型做事情即可,这样可以指挥一批对象做事情,即通过操作父类或接口实现。

——————————————————————————————————————————————————————

  class 毕姥爷{

    void 讲课(){

      System.out.println("企业管理");

    }

    void 钓鱼(){

      System.out.println("钓鱼");

    }

  }

  class 毕老师 extends 毕姥爷{

    void 讲课(){

      System.out.println("JAVA");

    }

    void 看电影(){

      System.out.println("看电影");

    }

  }

  class {

    public static void main(String[] args) {

      毕姥爷 x = new 毕老师(); //毕老师对象被提升为了毕姥爷类型。

      // x.讲课();

      // x.看电影(); //错误.

      毕老师 y = (毕老师)x; //将毕姥爷类型强制转换成毕老师类型。

      y.看电影();//在多态中,自始自终都是子类对象在做着类型的变化。

    }

  }

——————————————————————————————————————————————————————

  如果想用子类对象的特有方法,如何判断对象是哪个具体的子类类型呢?

    可以可以通过一个关键字 instanceof ;//判断对象是否实现了指定的接口或继承了指定的类

  格式:<对象 instanceof 类型> ,判断一个对象是否所属于指定的类型。

  Student instanceof Person = true;//student继承了person类

  多态在子父类中的成员上的体现的特点:

    1,成员变量:在多态中,子父类成员变量同名。

      在编译时期:参考的是引用型变量所属的类中是否有调用的成员。(编译时不产生对象,只检查语法错误)

      运行时期:也是参考引用型变量所属的类中是否有调用的成员。

      简单一句话:无论编译和运行,成员变量参考的都是引用变量所属的类中的成员变量。

      再说的更容易记忆一些:成员变量 ——— 编译运行都看 = 左边。

  2,成员函数。

    编译时期:参考引用型变量所属的类中是否有调用的方法。

    运行事情:参考的是对象所属的类中是否有调用的方法。

    为什么是这样的呢?因为在子父类中,对于一模一样的成员函数,有一个特性:覆盖。

    简单一句:成员函数,编译看引用型变量所属的类,运行看对象所属的类。

    更简单:成员函数 ——— 编译看 = 左边,运行看 = 右边。

  3,静态函数。

    编译时期:参考的是引用型变量所属的类中是否有调用的成员。

    运行时期:也是参考引用型变量所属的类中是否有调用的成员。

    为什么是这样的呢?因为静态方法,其实不所属于对象,而是所属于该方法所在的类。

    调用静态的方法引用是哪个类的引用调用的就是哪个类中的静态方法。

    简单说:静态函数 ——— 编译运行都看 = 左边。

——————————————————————————————————————————————————————

——————java.lang.Object

  Object:所有类的直接或者间接父类,Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是所有对象都具备的功能。

具体方法:

  1,boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。

  而根据对象的属性不同,判断对象是否相同的具体内容也不一样。所以在定义类时,一般都会复写equals方法,建立本类特有的判断对象是否相同的依据。

  public boolean equals(Object obj){

    if(!(obj instanceof Person))

    return false;

    Person p = (Person)obj;

    return this.age == p.age;

  }

  2,String toString():将对象变成字符串;默认返回的格式:类名@哈希值 = getClass().getName() + '@' + Integer.toHexString(hashCode())

  为了对象对应的字符串内容有意义,可以通过复写,建立该类对象自己特有的字符串表现形式。

  public String toString(){

    return "person : "+age;

  }

  3,Class getClass():获取任意对象运行时的所属字节码文件对象。

  4,int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。

  通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。

——————————————————————————————————————————————————————

  内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。

——————————————————————————————————————————————————————

class Outer{

  int num = 4;

  class Inner {

    void show(){

      System.out.println("inner show run "+num);

    }

  }

  public void method(){

  Inner in = new Inner();//创建内部类的对象。

  in.show();//调用内部类的方法。

  }

}

——————————————————————————————————————————————————————

  当内部类定义在外部类中的成员位置上,可以使用一些成员修饰符修饰 private、static。

  1:默认修饰符。

    直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象;

    Outer.Inner in = new Outer.new Inner();//这种形式很少用。

    但是这种应用不多见,因为内部类之所以定义在内部就是为了封装。想要获取内部类对象通常都通过外部类的方法来获取。这样可以对内部类对象进行控制。

  2:私有修饰符。

    通常内部类被封装,都会被私有化,因为封装性不让其他程序直接访问。

  3:静态修饰符。

    如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。

    注意;如果内部类中定义了静态成员,那么该内部类必须是静态的。

  内部类编译后的文件名为:“外部类名$内部类名.java”;

  为什么内部类可以直接访问外部类中的成员呢?

    那是因为内部中都持有一个外部类的引用。这个是引用是 外部类名.this

    内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。

    当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。

  匿名内部类:没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。

  匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。

  匿名内部类的使用场景:

    当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。

    其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。

——————————————————————————————————————————————————————

异 常:★★★★

  异常:就是不正常。程序在运行时出现的不正常情况。其实就是程序中出现的问题。这个问题按照面向对象思想进行描述,并封装成了对象。因为问题的产生有产生的原因、有问题的名称、有问题的描述等多个属性信息存在。当出现多属性信息最方便的方式就是将这些信息进行封装。异常就是java按照面向对象的思想将问题进行对象封装。这样就方便于操作问题以及处理问题。

  出现的问题有很多种,比如角标越界,空指针等都是。就对这些问题进行分类。而且这些问题都有共性内容比如:每一个问题都有名称,同时还有问题描述的信息,问题出现的位置,所以可以不断的向上抽取。形成了异常体系。

  ————————java.lang.Throwable:

  Throwable:可抛出的。

    |——Error:错误,一般情况下,不编写针对性的代码进行处理,通常是jvm发生的,需要对程序进行修正。

    |——Exception:异常,可以有针对性的处理方式

  无论是错误还是异常,它们都有具体的子类体现每一个问题,它们的子类都有一个共性,就是都以父类名才作为子类的后缀名。

  这个体系中的所有类和对象都具备一个独有的特点;就是可抛性。

  可抛性的体现:就是这个体系中的类和对象都可以被throws和throw两个关键字所操作。

——————————————————————————————————————————————————————

  class ExceptionDemo{

    public static void main(String[] args) {

      // byte[] buf = new byte[10241024700];//java.lang.OutOfMemoryError内存溢出错误

    }

  }

——————————————————————————————————————————————————————

  在开发时,如果定义功能时,发现该功能会出现一些问题,应该将问题在定义功能时标示出来,这样调用者就可以在使用这个功能的时候,预先给出处理方式。

  如何标示呢?通过throws关键字完成,格式:throws 异常类名,异常类名...

  这样标示后,调用者,在使用该功能时,就必须要处理,否则编译失败。

  处理方式有两种:1、捕捉;2、抛出。

  对于捕捉:java有针对性的语句块进行处理。

  try {

    需要被检测的代码;

  }

  catch(异常类 变量名){

    异常处理代码;

  }

  fianlly{

  一定会执行的代码;

  }

——————————————————————————————————————————————————————

  catch (Exception e) { //e用于接收try检测到的异常对象。

    System.out.println("message:"+e.getMessage());//获取的是异常的信息。

    System.out.println("toString:"+e.toString());//获取的是异常的名字+异常的信息。

    e.printStackTrace();//打印异常在堆栈中信息;异常名称+异常信息+异常的位置。

  }

——————————————————————————————————————————————————————

  异常处理原则:功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。

  特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。

  throw 和throws关键字的区别:

    throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。

    throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。

  通常情况:函数内容如果有throw,抛出异常对象,并没有进行处理,那么函数上一定要声明,否则编译失败。但是也有特殊情况。

  异常分两种:

    1:编译时被检查的异常,只要是Exception及其子类都是编译时被检测的异常。

    2:运行时异常,其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常。

  编译时被检查的异常和运行时异常的区别:

    编译被检查的异常在函数内被抛出,函数必须要声明,否编译失败。

    声明的原因:是需要调用者对该异常进行处理。

    运行时异常如果在函数内被抛出,在函数上不需要声明。

    不声明的原因:不需要调用者处理,运行时异常发生,已经无法再让程序继续运行,所以,不让调用处理的,直接让程序停止,由调用者对代码进行修正。

  定义异常处理时,什么时候定义try,什么时候定义throws呢?

    功能内部如果出现异常,如果内部可以处理,就用try;

    如果功能内部处理不了,就必须声明出来,让调用者处理。

  自定义异常:当开发时,项目中出现了java中没有定义过的问题时,这时就需要我们按照java异常建立思想,将项目的中的特有问题也进行对象的封装。这个异常,称为自定义异常。

  对于除法运算,0作为除数是不可以的。java中对这种问题用ArithmeticException类进行描述。对于这个功能,在我们项目中,除数除了不可以为0外,还不可以为负数。可是负数的部分java并没有针对描述。所以我们就需要自定义这个异常。

  自定义异常的步骤:

    1:定义一个子类继承Exception或RuntimeException,让该类具备可抛性。

    2:通过throw 或者throws进行操作。

  异常的转换思想:当出现的异常是调用者处理不了的,就需要将此异常转换为一个调用者可以处理的异常抛出。

  try catch finally的几种结合方式:

  1,

    try

    catch

    finally

  2,

    try

    catch

  3,

    try

    finally

  这种情况,如果出现异常,并不处理,但是资源一定关闭,所以try finally集合只为关闭资源。

  记住:finally很有用,主要用户关闭资源。无论是否发生异常,资源都必须进行关闭。

  System.exit(0); //退出jvm,只有这种情况finally不执行。

  当异常出现后,在子父类进行覆盖时,有了一些新的特点:

    1:当子类覆盖父类的方法时,如果父类的方法抛出了异常,那么子类的方法要么不抛出异常要么抛出父类异常或者该异常的子类,不能抛出其他异常。

    2:如果父类抛出了多个异常,那么子类在覆盖时只能抛出父类的异常的子集。

  注意:

    如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws。

    如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。

  常见异常:

    1、脚标越界异常(IndexOutOfBoundsException)包括数组、字符串;

      空指针异常(NullPointerException)

    2、类型转换异常:ClassCastException

    3、没有这个元素异常:NullPointerException

    4、不支持操作异常;

  异常要尽量避免,如果避免不了,需要预先给出处理方式。比如家庭备药,比如灭火器。

——————————————————————————————————————————————————————

包:定义包用package关键字。

  1:对类文件进行分类管理。

  2:给类文件提供多层名称空间。

  如果生成的包不在当前目录下,需要最好执行classpath,将包所在父目录定义到classpath变量中即可。

  一般在定义包名时,因为包的出现是为了区分重名的类。所以包名要尽量唯一。怎么保证唯一性呢?可以使用url域名来进行包名称的定义。

  package pack;//定义了一个包,名称为pack。 注意:包名的写法规范:所有字母都小写。

  //package cn.itcast.pack.demo;

  类的全名称是 包名.类名

编译命令:javac –d 位置(.当前路径) java源文件 (就可以自动生成包)

  包是一种封装形式,用于封装类,想要被包以外的程序访问,该类必须public;

类中的成员,如果被包以外访问,也必须public;

  包与包之间访问可以使用的权限有两种:

    1:public

    2:protected:只能是不同包中的子类可以使用的权限。

  总结java中的四种权限:

  范围        public   protected   default   private 

  同一个类中 ok     ok       ok      ok

  同一包中    ok     ok       ok

  子类       ok

  不同包中 ok

——————————————————————————————————————————————————————

多线程:★★★★

  进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。

  线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。

  一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。

  jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

  当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。

  随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。

  返回当前线程的名称:Thread.currentThread().getName()

  线程的名称是由:Thread—编号定义的。编号从0开始。

  线程要运行的代码都统一存放在了run方法中。

  线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)

  start方法:1)、启动了线程;2)、让jvm调用了run方法。

  创建线程的第一种方式:继承Thread ,由子类复写run方法。

  步骤:

    1,定义类继承Thread类;

    2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;

    3,通过创建Thread类的子类对象,创建线程对象;

    4,调用线程的start方法,开启线程,并执行run方法。

  线程状态:

    被创建:start()

    运行:具备执行资格,同时具备执行权;

    冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

    临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

    消亡:stop()

创建线程的第二种方式:实现一个接口Runnable。

  步骤:

    1,定义类实现Runnable接口。

    2,覆盖接口中的run方法(用于封装线程要运行的代码)。

    3,通过Thread类创建线程对象;

    4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。

      为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。

    5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。

      Ticket t = new Ticket();

      /*直接创建Ticket对象,并不是创建线程对象。

      因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。

      所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。*/

      Thread t1 = new Thread(t); //创建线程。

      /*只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联

      为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。*/

      t1.start();

  为什么要有Runnable接口的出现?

  1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。

    可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?

    只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。

    所以,通常创建线程都用第二种方式。

    因为实现Runnable接口可以避免单继承的局限性。

  2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。

    所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。

    实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。

——————————————————————————————————————————————————————

  //面试

    new Thread(new Runnable(){ //匿名

      public void run(){

        System.out.println("runnable run");

      }

    })

    {

      public void run(){

        System.out.println("subthread run");

      }

    }.start(); //结果:subthread run

——————————————————————————————————————————————————————

    Try {

      Thread.sleep(10);

    }catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu 切换情况。

  多线程安全问题的原因:

    通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。

  涉及到两个因素:

    1,多个线程在操作共享数据。

    2,有多条语句对共享数据进行运算。

      原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。

  解决安全问题的原理:

    只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

  如何进行多句操作共享数据代码的封装呢?

    java中提供了一个解决方式:就是同步代码块。

    格式:

    synchronized(对象) { // 任意对象都可以。这个对象就是锁。

      需要被同步的代码;

    }

——————————————————————————————————————————————————————

同步:★★★★★

  好处:解决了线程安全问题。

  弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

  定义同步是有前提的:

    1,必须要有两个或者两个以上的线程,才需要同步。

    2,多个线程必须保证使用的是同一个锁。

  同步的第二种表现形式:

    同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。

    同步函数是用的哪个锁呢?

      通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。

   当同步函数被static修饰时,这时的同步用的是哪个锁呢?

    静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。

    所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。

    这个对象就是 类名.class

  同步代码块和同步函数的区别?

    同步代码块使用的锁可以是任意对象。

    同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

    在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

——————————————————————————————————————————————————————

  ★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。

  //懒汉式:延迟加载方式。

  当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。

  为了效率问题,通过双重判断的形式解决。

  class Single{

    private static Single s = null;

    private Single(){}

    public static Single getInstance(){ //锁是谁?字节码文件对象;

    if(s == null){

      synchronized(Single.class){

        if(s == null)

        s = new Single();

      }

    }

    return s;

    }

  }

——————————————————————————————————————————————————————

  同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

  线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。

    1:将资源封装成对象。

    2:将线程执行的任务(任务其实就是run方法。)也封装成对象。

  等待唤醒机制:涉及的方法:

    wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

    notify:唤醒线程池中某一个等待线程。

    notifyAll:唤醒的是线程池中的所有线程。

  注意:

    1:这些方法都需要定义在同步中。

    2:因为这些方法必须要标示所属的锁。

      你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

    3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

      因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,

      那么能被任意对象调用的方法一定定义在Object类中。

  wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:

    wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

    sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

    wait:线程会释放执行权,而且线程会释放锁。

    Sleep:线程会释放执行权,但不是不释放锁。

  线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。

    停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。

    怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

    第一种方式:定义循环的结束标记。

    第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

  —————————< java.lang.Thread >————

    interrupt():中断线程。

    setPriority(int newPriority):更改线程的优先级。

    getPriority():返回线程的优先级。

    toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

    Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

    setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,

             Java 虚拟机退出。该方法必须在启动线程前调用。

    join:临时加入一个线程的时候可以使用join方法。

      当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。

      A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

——————————————————————————————————————————————————————

Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。

  解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。

  到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。

  在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。

  所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。

  在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

  而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。

< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();

——————————————————————————————————————————————————————

  class BoundedBuffer {

   final Lock lock = new ReentrantLock();

   final Condition notFull = lock.newCondition();

   final Condition notEmpty = lock.newCondition();

   final Object[] items = new Object[100];

   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {

   lock.lock();

   try {

   while (count == items.length)

       notFull.await();

     items[putptr] = x; 

     if (++putptr == items.length) putptr = 0;

     ++count;

     notEmpty.signal();

   }

    finally {

       lock.unlock();

   }

  }

  public Object take() throws InterruptedException {

     lock.lock();

     try {

       while (count == 0)

       notEmpty.await();

       Object x = items[takeptr];

        if (++takeptr == items.length) takeptr = 0;

       ——count;

       notFull.signal();

        return x;

     } 

      finally {

         lock.unlock();

     }

   }

   }

——————————————————————————————————————————————————————

  API:(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

  ——< java.lang >—— String字符串:★★★☆

  java中用String类进行描述。对字符串进行了对象的封装。这样的好处是可以对字符串这种常见数据进行方便的操作。对象封装后,可以定义N多属性和行为。

  如何定义字符串对象呢?String s = "abc";只要是双引号引起的数据都是字符串对象。

  特点:字符串一旦被初始化,就不可以被改变,存放在方法区中的常量池中。

——————————————————————————————————————————————————————

    String s1 = "abc"; // s1指向的内存中只有一个对象abc。

    String s2 = new String("abc"); // s2指向的内容中有两个对象abc、new 。

    System.out.println(s1==s2);//false

    System.out.println(s1.equals(s2));//true ,字符串中equals比较的是字符串内容是否相同。

——————————————————————————————————————————————————————

  字符串的方法:

  1:构造方法:将字节数组或者字符数组转成字符串。

    String s1 = new String();//创建了一个空内容的字符串。

    String s2 = null;//s2没有任何对象指向,是一个null常量值。

    String s3 = "";//s3指向一个具体的字符串对象,只不过这个字符串中没有内容。

    //一般在定义字符串时,不用new。

    String s4 = new String("abc");

    String s5 = "abc"; 一般用此写法

    new String(char[]);//将字符数组转成字符串。

    new String(char[],offset,count);//将字符数组中的一部分转成字符串。

  2:一般方法:

      按照面向对象的思想:

    2.1 获取:

      2.1.1:获取字符串的长度。length();

      2.1.2:指定位置的字符。char charAt(int index);

      2.1.3:获取指定字符的位置。如果不存在返回—1,所以可以通过返回值—1来判断某一个字符不存在的情况。

        int indexOf(int ch);//返回第一次找到的字符角标

        int indexOf(int ch,int fromIndex); //返回从指定位置开始第一次找到的角标

        int indexOf(String str); //返回第一次找到的字符串角标

        int indexOf(String str,int fromIndex);

        int lastIndexOf(int ch);

        int lastIndexOf(int ch,int fromIndex);

        int lastIndexOf(String str);

        int lastIndexOf(String str,int fromIndex);

      2.1.4:获取子串。

        String substring(int start);//从start位开始,到length()—1为止.

        String substring(int start,int end);//从start开始到end为止。//包含start位,不包含end位。

        substring(0,str.length());//获取整串

    2.2 判断:

      2.2.1:字符串中包含指定的字符串吗?

        boolean contains(String substring);

      2.2.2:字符串是否以指定字符串开头啊?

        boolean startsWith(string);

      2.2.3:字符串是否以指定字符串结尾啊?

        boolean endsWith(string);

      2.2.4:判断字符串是否相同

        boolean equals(string);//覆盖了Object中的方法,判断字符串内容是否相同。

      2.2.5:判断字符串内容是否相同,忽略大小写。

        boolean equalsIgnoreCase(string) ;

    2.3 转换:

      2.3.1:通过构造函数可以将字符数组或者字节数组转成字符串。

      2.3.2:可以通过字符串中的静态方法,将字符数组转成字符串。

        static String copyValueOf(char[] );

        static String copyValueOf(char[],int offset,int count);

        static String valueOf(char[]);

        static String valueOf(char[],int offset,int count);

      2.3.3:将基本数据类型或者对象转成字符串。

        static String valueOf(char);

        static String valueOf(boolean);

        static String valueOf(double);

        static String valueOf(float);

        static String valueOf(int);

        static String valueOf(long);

        static String valueOf(Object);

      2.3.4:将字符串转成大小写。

        String toLowerCase();

        String toUpperCase();

      2.3.5:将字符串转成数组。

        char[] toCharArray();//转成字符数组。

        byte[] getBytes();//可以加入编码表。转成字节数组。

      2.3.6:将字符串转成字符串数组。切割方法。

        String[] split(分割的规则—字符串);

      2.3.7:将字符串进行内容替换。注意:修改后变成新字符串,并不是将原字符串直接修改。

        String replace(oldChar,newChar);

        String replace(oldstring,newstring);

      2.3.8: String concat(string); //对字符串进行追加。

        String trim();//去除字符串两端的空格

        int compareTo();//如果参数字符串等于此字符串,则返回值 0;如果此字符串按字典顺序小于字符串参数,

                则返回一个小于 0 的值;如果此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。

——————————————————————————————————————————————————————

——< java.lang >—— StringBuffer字符串缓冲区:★★★☆

  构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符。

  特点:

    1:可以对字符串内容进行修改。

    2:是一个容器。

    3:是可变长度的。

    4:缓冲区中可以存储任意类型的数据。

    5:最终需要变成字符串。

  容器通常具备一些固定的方法:

  1,添加。

    StringBuffer append(data):在缓冲区中追加数据。追加到尾部。

    StringBuffer insert(index,data):在指定位置插入数据。

  2,删除。

    StringBuffer delete(start,end);删除从start至end—1范围的元素

    StringBuffer deleteCharAt(index);删除指定位置的元素

    //sb.delete(0,sb.length());//清空缓冲区。

  3,修改。

    StringBuffer replace(start,end,string);将start至end—1替换成string

    void setCharAt(index,char);替换指定位置的字符

    void setLength(len);将原字符串置为指定长度的字符串

  4,查找。(查不到返回—1)

    int indexOf(string); 返回指定子字符串在此字符串中第一次出现处的索引。

    int indexOf(string,int fromIndex);从指定位置开始查找字符串

    int lastIndexOf(string); 返回指定子字符串在此字符串中最右边出现处的索引。

    int lastIndexOf(string,int fromIndex); 从指定的索引开始反向搜索

  5,获取子串。

    string substring(start); 返回start到结尾的子串

    string substring(start,end); 返回start至end—1的子串

  6,反转。

    StringBuffer reverse();字符串反转

——————————————————————————————————————————————————————

——< java.lang >—— StringBuilder字符串缓冲区:★★★☆

  JDK1.5出现StringBuiler;构造一个其中不带字符的字符串生成器,初始容量为 16 个字符。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

  方法和StringBuffer一样;

  StringBuffer 和 StringBuilder 的区别:

    StringBuffer线程安全。

    StringBuilder线程不安全。

    单线程操作,使用StringBuilder 效率高。

    多线程操作,使用StringBuffer 安全。

——————————————————————————————————————————————————————

      StringBuilder sb = new StringBuilder("abcdefg");

      sb.append("ak"); //abcdefgak

      sb.insert(1,"et");//aetbcdefg

      sb.deleteCharAt(2);//abdefg

      sb.delete(2,4);//abefg

      sb.setLength(4);//abcd

      sb.setCharAt(0,'k');//kbcdefg

      sb.replace(0,2,"hhhh");//hhhhcdefg

      //想要使用缓冲区,先要建立对象。

      StringBuffer sb = new StringBuffer();

      sb.append(12).append("haha");//方法调用链。

      String s = "abc"+4+'q';

      s = new StringBuffer().append("abc").append(4).append('q').toString();

——————————————————————————————————————————————————————

  class Test{

    public static void main(String[] args) {

      String s1 = "java";

      String s2 = "hello";

      method_1(s1,s2);

      System.out.println(s1+"...."+s2); //java....hello

      StringBuilder s11 = new StringBuilder("java");

      StringBuilder s22 = new StringBuilder("hello");

      method_2(s11,s22);

      System.out.println(s11+"—————"+s22); //javahello—————hello

    }

    public static void method_1(String s1,String s2){

      s1.replace('a','k');

      s1 = s2;

    }

    public static void method_2(StringBuilder s1,StringBuilder s2){

      s1.append(s2);

      s1 = s2;

    }

  }

——————————————————————————————————————————————————————

  基本数据类型对象包装类:是按照面向对象思想将基本数据类型封装成了对象。

  好处:

    1:可以通过对象中的属性和行为操作基本数据。

    2:可以实现基本数据类型和字符串之间的转换。

  关键字 对应的类名

    byte Byte

    short Short paserShort(numstring);

    int Integer 静态方法:parseInt(numstring)

    long Long

    float Float

    double Double

    char Character

    Boolean Boolean

    基本数据类型对象包装类:都有 XXX parseXXX 方法

    只有一个类型没有parse方法:Character ;

——————————————————————————————————————————————————————

Integer对象: ★★★☆

  数字格式的字符串转成基本数据类型的方法:

    1:将该字符串封装成了Integer对象,并调用对象的方法intValue();

    2:使用Integer.parseInt(numstring):不用建立对象,直接类名调用;

  将基本类型转成字符串:

    1:Integer中的静态方法 String toString(int);

    2:int+"";

  将一个十进制整数转成其他进制:

    转成二进制:toBinaryString

    转成八进制:toOctalString

    转成十六进制:toHexString

      toString(int num,int radix);

  将其他进制转换十进制:

    parseInt(string,radix); //将给定的数转成指定的基数进制;

  在jdk1.5版本后,对基本数据类型对象包装类进行升级。在升级中,使用基本数据类型对象包装类可以像使用基本数据类型一样,进行运算。

    Integer i = new Integer(4); //1.5版本之前的写法

    Integer i = 4; //自动装箱,1.5版本后的写法;

    i = i + 5;

    //i对象是不能直接和5相加的,其实底层先将i转成int类型,在和5相加。而转成int类型的操作是隐式的。自动拆箱:拆箱的原理就是i.intValue();i+5运算完是一个int整数。如何赋值给引用类型i呢?其实有对结果进行装箱。

    Integer c = 127;

    Integer d = 127;

    System.out.println(c = = d); //true

    //在装箱时,如果数值在byte范围之内,那么数值相同,不会产生新的对象,也就是说多个数值相同的引用指向的是同一个对象。

——————————————————————————————————————————————————————

集合框架:★★★★★,用于存储数据的容器。

  特点:

    1:对象封装数据,对象多了也需要存储。集合用于存储对象。

    2:对象的个数确定可以使用数组,但是不确定怎么办?可以用集合。因为集合是可变长度的。

  集合和数组的区别:

    1:数组是固定长度的;集合可变长度的。

    2:数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。

    3:数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。

  数据结构:就是容器中存储数据的方式。

  对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。

  集合容器在不断向上抽取过程中。出现了集合体系。

  在使用一个体系时,原则:参阅顶层内容。建立底层对象。

——————————————————————————————————————————————————————

——< java.util >—— Collection接口:

  Collection:

    |——List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

    |——Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。

  1,添加:

    add(object):添加一个元素

    addAll(Collection) :添加一个集合中的所有元素。

  2,删除:

    clear():将集合中的元素全删除,清空集合。

    remove(obj) :删除集合中指定的对象。注意:删除成功,集合的长度会改变。

    removeAll(collection) :删除部分元素。部分元素和传入Collection一致。

  3,判断:

    boolean contains(obj) :集合中是否包含指定元素 。

    boolean containsAll(Collection) :集合中是否包含指定的多个元素。

    boolean isEmpty():集合中是否有元素。

  4,获取:

    int size():集合中有几个元素。

  5,取交集:

    boolean retainAll(Collection) :对当前集合中保留和指定集合中的相同的元素。如果两个集合元素相同,返回flase;如果retainAll修改了当前集合,返回true。

  6,获取集合中所有元素:

    Iterator iterator():迭代器

  7,将集合变成数组:

    toArray();

——————————————————————————————————————————————————————

——< java.util >—— Iterator接口:

  迭代器:是一个接口。作用:用于取集合中的元素。

每一个集合都有自己的数据结构,都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口。

  也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。

  Iterator it = coll.iterator();//获取容器中的迭代器对象,至于这个对象是是什么不重要。这对象肯定符合一个规则Iterator接口。

——————————————————————————————————————————————————————

  public static void main(String[] args) {

    Collection coll = new ArrayList();

    coll.add("abc0");

    coll.add("abc1");

    coll.add("abc2");

    //————————方式1——————————

    Iterator it = coll.iterator();

    while(it.hasNext()){

      System.out.println(it.next());

    }

    //—————————方式2用此种——————————

    for(Iterator it = coll.iterator();it.hasNext(); ){

      System.out.println(it.next());

    }

  }

——————————————————————————————————————————————————————

——< java.util >—— List接口:

  List本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。

  List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。

    |——ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。

    |——LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。

    |——Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。

  1,添加:

    add(index,element) :在指定的索引位插入元素。

    addAll(index,collection) :在指定的索引位插入一堆元素。

  2,删除:

    remove(index) :删除指定索引位的元素。 返回被删的元素。

  3,获取:

    Object get(index) :通过索引获取指定元素。

    int indexOf(obj) :获取指定元素第一次出现的索引位,如果该元素不存在返回—1;

    所以,通过—1,可以判断一个元素是否存在。

    int lastIndexOf(Object o) :反向索引指定元素的位置。

    List subList(start,end) :获取子列表。

  4,修改:

    Object set(index,element) :对指定索引位进行元素的修改。

  5,获取所有元素:

    ListIterator listIterator():list集合特有的迭代器。

  List集合支持对元素的增、删、改、查。

  List集合因为角标有了自己的获取元素的方式: 遍历。

    for(int x=0; x<list.size(); x++){

      sop("get:"+list.get(x));

    }

    在进行list列表元素迭代的时候,如果想要在迭代过程中,想要对元素进行操作的时候,比如满足条件添加新元素。会发生.ConcurrentModificationException并发修改异常。

  导致的原因是:

    集合引用和迭代器引用在同时操作元素,通过集合获取到对应的迭代器后,在迭代中,进行集合引用的元素添加,迭代器并不知道,所以会出现异常情况。

  如何解决呢?

    既然是在迭代中对元素进行操作,找迭代器的方法最为合适.可是Iterator中只有hasNext,next,remove方法.通过查阅的它的子接口,ListIterator,发现该列表迭代器接口具备了对元素的增、删、改、查的动作。

  ListIterator是List集合特有的迭代器。

  ListIterator it = list.listIterator;//取代Iterator it = list.iterator;

可变长度数组的原理:

    当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。

    ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。

    Vector:是按照原数组的100%延长。

  注意:对于list集合,底层判断元素是否相同,其实用的是元素自身的equals方法完成的。所以建议元素都要复写equals方法,建立元素对象自己的比较相同的条件依据。

  LinkedList:的特有方法。

    addFirst();

    addLast();

  在jdk1.6以后。

    offerFirst();

    offerLast();

    getFirst():获取链表中的第一个元素。如果链表为空,抛出NoSuchElementException;

    getLast();

  在jdk1.6以后。

    peekFirst();获取链表中的第一个元素。如果链表为空,返回null。

    peekLast();

    removeFirst():获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,抛出NoSuchElementException

    removeLast();

  在jdk1.6以后。

    pollFirst();获取链表中的第一个元素,但是会删除链表中的第一个元素。如果链表为空,返回null。

    pollLast();

——————————————————————————————————————————————————————

——< java.util >—— Set接口:

  Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器。

    |——HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;

        HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。

        当元素的hashCode值相同时,才继续判断元素的equals是否为true。

        如果为true,那么视为相同元素,不存。如果为false,那么存储。

        如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。

      |——LinkedHashSet:有序,hashset的子类。

    |——TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。

  哈希表的原理:

    1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。

    2,哈希值就是这个元素的位置。

    3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。

    4,存储哈希值的结构,我们称为哈希表。

    5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。

      这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

  对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。

  对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。

  TreeSet:

    用于对Set集合进行元素的指定顺序排序,排序需要依据元素自身具备的比较性。

    如果元素不具备比较性,在运行时会发生ClassCastException异常。

    所以需要元素实现Comparable接口,强制让元素具备比较性,复写compareTo方法。

    依据compareTo方法的返回值,确定元素在TreeSet数据结构中的位置。

    TreeSet方法保证元素唯一性的方式:就是参考比较方法的结果是否为0,如果return 0,视为两个对象重复,不存。

  注意:在进行比较时,如果判断元素不唯一,比如,同姓名,同年龄,才视为同一个人。

    在判断时,需要分主要条件和次要条件,当主要条件相同时,再判断次要条件,按照次要条件排序。

  TreeSet集合排序有两种方式,Comparable和Comparator区别:

    1:让元素自身具备比较性,需要元素对象实现Comparable接口,覆盖compareTo方法。

    2:让集合自身具备比较性,需要定义一个实现了Comparator接口的比较器,并覆盖compare方法,并将该类对象作为实际参数传递给TreeSet集合的构造函数。

    第二种方式较为灵活。

——————————————————————————————————————————————————————

  Map集合:

    |——Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。

    |——HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable.

    |——TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。

  Map集合存储和Collection有着很大不同:

    Collection一次存一个元素;Map一次存一对元素。

    Collection是单列集合;Map是双列集合。

    Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。

    特点:要保证map集合中键的唯一性。

  1,添加。

     put(key,value):当存储的键相同时,新的值会替换老的值,并将老值返回。如果键没有重复,返回null。

    void putAll(Map);

  2,删除。

    void clear():清空

    value remove(key) :删除指定键。

  3,判断。

    boolean isEmpty():

    boolean containsKey(key):是否包含key

    boolean containsValue(value) :是否包含value

  4,取出。

    int size():返回长度

    value get(key) :通过指定键获取对应的值。如果返回null,可以判断该键不存在。当然有特殊情况,就是在hashmap集合中,是可以存储null键null值的。

    Collection values():获取map集合中的所有的值。

  5,想要获取map中的所有元素:

    原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。

★ 把map集合转成set的方法:

  Set keySet();

  Set entrySet();//取的是键和值的映射关系。

   Entry就是Map接口中的内部接口;

   为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对。

——————————————————————————————————————————————————————

  取出map集合中所有元素的方式一:keySet()方法。

    可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行值的获取。

      Set keySet = map.keySet();

      Iterator it = keySet.iterator();

      while(it.hasNext()) {

        Object key = it.next();

        Object value = map.get(key);

        System.out.println(key+":"+value);

      }

——————————————————————————————————————————————————————

  取出map集合中所有元素的方式二:entrySet()方法。

      Set entrySet = map.entrySet();

      Iterator it = entrySet.iterator();

      while(it.hasNext()) {

        Map.Entry me = (Map.Entry)it.next();

        System.out.println(me.getKey()+"::::"+me.getValue());

      }

——————————————————————————————————————————————————————

  使用集合的技巧:

    看到Array就是数组结构,有角标,查询速度很快。

    看到link就是链表结构:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();

    看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到该结构的中的元素必须覆盖hashCode,equals方法。

    看到tree就是二叉树,就要想到排序,就想要用到比较。

  比较的两种方式:

    一个是Comparable:覆盖compareTo方法;

    一个是Comparator:覆盖compare方法。

  LinkedHashSet,LinkedHashMap:这两个集合可以保证哈希表有存入顺序和取出顺序一致,保证哈希表有序。

  集合什么时候用?

    当存储的是一个元素时,就用Collection。当存储对象之间存在着映射关系时,就使用Map集合。

  保证唯一,就用Set。不保证唯一,就用List。

——————————————————————————————————————————————————————

  Collections:它的出现给集合操作提供了更多的功能。这个类不需要创建对象,内部提供的都是静态方法。

  静态方法:

    Collections.sort(list);//list集合进行元素的自然顺序排序。

    Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。

    class ComparatorByLen implements Comparator{

      public int compare(String s1,String s2){

        int temp = s1.length()—s2.length();

        return temp==0?s1.compareTo(s2):temp;

      }

    }

Collections.max(list); //返回list中字典顺序最大的元素。

    int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。

    Collections.reverseOrder();//逆向反转排序。

    Collections.shuffle(list);//随机对list中的元素进行位置的置换。

   将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);

    List synchronizedList(list);

    Map synchronizedMap(map);

    原理:定义一个类,将集合所有的方法加同一把锁后返回。

  Collection 和 Collections的区别:

    Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。

    Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

——————————————————————————————————————————————————————

  Arrays:

    用于操作数组对象的工具类,里面都是静态方法。

    asList方法:将数组转换成list集合。

    String[] arr = {"abc","kk","qq"};

    List list = Arrays.asList(arr);//将arr数组转成list集合。

    将数组转换成集合,有什么好处呢?用aslist方法,将数组变成集合;

    可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set;

    注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);

    如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。

    如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。

  集合变数组:用的是Collection接口中的方法:toArray();

    如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。

    如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他为存储元素的位置默认值null。

    所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。

   将集合变成数组后有什么好处?限定了对集合中的元素进行增删操作,只要获取这些元素即可。

——————————————————————————————————————————————————————

  Jdk5.0新特性:

  Collection在jdk1.5以后,有了一个父接口Iterable,这个接口的出现的将iterator方法进行抽取,提高了扩展性。

——————————————————————————————————————————————————————

  增强for循环:foreach语句,foreach简化了迭代器。

    格式:// 增强for循环括号里写两个参数,第一个是声明一个变量,第二个就是需要迭代的容器

    for( 元素类型 变量名 : Collection集合 & 数组 ) {

      …

    }

  高级for循环和传统for循环的区别:

    高级for循环在使用时,必须要明确被遍历的目标。这个目标,可以是Collection集合或者数组,如果遍历Collection集合,在遍历过程中还需要对元素进行操作,比如删除,需要使用迭代器。

    如果遍历数组,还需要对数组元素进行操作,建议用传统for循环因为可以定义角标通过角标操作元素。如果只为遍历获取,可以简化成高级for循环,它的出现为了简化书写。

  高级for循环可以遍历map集合吗?不可以。但是可以将map转成set后再使用foreach语句。

  1)、作用:对存储对象的容器进行迭代: 数组 collection map

  2)、增强for循环迭代数组:

    String [] arr = {"a", "b", "c"};//数组的静态定义方式,只试用于数组首次定义的时候

    for(String s : arr) {

      System.out.println(s);

    }

  3)、单列集合 Collection:

    List list = new ArrayList();

    list.add("aaa");

    // 增强for循环, 没有使用泛型的集合能不能使用增强for循环迭代?能

    for(Object obj : list) {

      String s = (String) obj;

      System.out.println(s);

    }

  4)、双列集合 Map:

    Map map = new HashMap();

    map.put("a", "aaa");

    // 传统方式:必须掌握这种方式

    Set entrys = map.entrySet(); // 1.获得所有的键值对Entry对象

    iter = entrys.iterator(); // 2.迭代出所有的entry

    while(iter.hasNext()) {

      Map.Entry entry = (Entry) iter.next();

      String key = (String) entry.getKey(); // 分别获得key和value

      String value = (String) entry.getValue();

      System.out.println(key + "=" + value);

    }

    // 增强for循环迭代:原则上map集合是无法使用增强for循环来迭代的,因为增强for循环只能针对实现了Iterable接口的集合进行迭代;Iterable是jdk5中新定义的接口,就一个方法iterator方法,只有实现了Iterable接口的类,才能保证一定有iterator方法,java有这样的限定是因为增强for循环内部还是用迭代器实现的,而实际上,我们可以通过某种方式来使用增强for循环。

    for(Object obj : map.entrySet()) {

      Map.Entry entry = (Entry) obj; // obj 依次表示Entry

      System.out.println(entry.getKey() + "=" + entry.getValue());

    }

  5)、集合迭代注意问题:在迭代集合的过程中,不能对集合进行增删操作(会报并发访问异常);可以用迭代器的方法进行操作(子类listIterator:有增删的方法)。

  6)、增强for循环注意问题:在使用增强for循环时,不能对元素进行赋值;

    int[] arr = {1,2,3};

    for(int num : arr) {

      num = 0; //不能改变数组的值

    }

    System.out.println(arr[1]); //2

——————————————————————————————————————————————————————

  可变参数(...):用到函数的参数上,当要操作的同一个类型元素个数不确定的时候,可是用这个方式,这个参数可以接受任意个数的同一类型的数据。

  和以前接收数组不一样的是:

    以前定义数组类型,需要先创建一个数组对象,再将这个数组对象作为参数传递给函数。现在,直接将数组中的元素作为参数传递即可。底层其实是将这些元素进行数组的封装,而这个封装动作,是在底层完成的,被隐藏了。所以简化了用户的书写,少了调用者定义数组的动作。

    如果在参数列表中使用了可变参数,可变参数必须定义在参数列表结尾(也就是必须是最后一个参数,否则编译会失败。)。

    如果要获取多个int数的和呢?可以使用将多个int数封装到数组中,直接对数组求和即可。

——————————————————————————————————————————————————————

  静态导入:导入了类中的所有静态成员,简化静态成员的书写。

    import static java.util.Collections.*; //导入了Collections类中的所有静态成员

——————————————————————————————————————————————————————

  枚举:关键字 enum

    问题:对象的某个属性的值不能是任意的,必须为固定的一组取值其中的某一个;

  解决办法:

    1)、在setGrade方法中做判断,不符合格式要求就抛出异常;

    2)、直接限定用户的选择,通过自定义类模拟枚举的方式来限定用户的输入,写一个Grade类,私有构造函数,对外提供5个静态的常量表示类的实例;

    3)、jdk5中新定义了枚举类型,专门用于解决此类问题;

    4)、枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类;

——————————————————————————————————————————————————————

  自动拆装箱:java中数据类型分为两种 : 基本数据类型 引用数据类型(对象)

    在 java程序中所有的数据都需要当做对象来处理,针对8种基本数据类型提供了包装类,如下:

      int ——> Integer

      byte ——> Byte

      short ——> Short

      long ——> Long

      char ——> Character

      double ——> Double

      float ——> Float

      boolean ——> Boolean

  jdk5以前基本数据类型和包装类之间需要互转:

    基本———引用 Integer x = new Integer(x);

    引用———基本 int num = x.intValue();

      1)、Integer x = 1; x = x + 1; 经历了什么过程?装箱 ——> 拆箱——> 装箱;

      2)、为了优化,虚拟机为包装类提供了缓冲池,Integer池的大小 —128~127 一个字节的大小;

      3)、String池:Java为了优化字符串操作 提供了一个缓冲池;

——————————————————————————————————————————————————————

  泛型:jdk1.5版本以后出现的一个安全机制。表现格式:< >

  好处:

    1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。

    2:避免了强制转换的麻烦。

  只要带有<>的类或者接口,都属于带有类型参数的类或者接口,在使用这些类或者接口时,必须给<>中传递一个具体的引用数据类型。

  泛型技术:其实应用在编译时期,是给编译器使用的技术,到了运行时期,泛型就不存在了。

  为什么? 因为泛型的擦除:也就是说,编辑器检查了泛型的类型正确后,在生成的类文件中是没有泛型的。

  在运行时,如何知道获取的元素类型而不用强转呢?

  泛型的补偿:因为存储的时候,类型已经确定了是同一个类型的元素,所以在运行时,只要获取到该元素的类型,在内部进行一次转换即可,所以使用者不用再做转换动作了。

  什么时候用泛型类呢?

    当类中的操作的引用数据类型不确定的时候,以前用的Object来进行扩展的,现在可以用泛型来表示。这样可以避免强转的麻烦,而且将运行问题转移到的编译时期。

——————————————————————————————————————————————————————

  泛型在程序定义上的体现:

  //泛型类:将泛型定义在类上。

  class Tool {

    private Q obj;

    public void setObject(Q obj) {

      this.obj = obj;

    }

    public Q getObject() {

      return obj;

    }

  }

  //当方法操作的引用数据类型不确定的时候,可以将泛型定义在方法上。

  public void method(W w) {

    System.out.println("method:"+w);

  }

  //静态方法上的泛型:静态方法无法访问类上定义的泛型。如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

  public static void function(Q t) {

    System.out.println("function:"+t);

  }

  //泛型接口.

  interface Inter {

    void show(T t);

  }

  class InterImpl implements Inter {

    public void show(R r) {

      System.out.println("show:"+r);

    }

  }

——————————————————————————————————————————————————————

    泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

  泛型限定:

    上限:?extends E:可以接收E类型或者E的子类型对象。

    下限:?super E:可以接收E类型或者E的父类型对象。

  上限什么时候用:往集合中添加元素时,既可以添加E类型对象,又可以添加E的子类型对象。为什么?因为取的时候,E类型既可以接收E类对象,又可以接收E的子类型对象。

  下限什么时候用:当从集合中获取元素进行操作的时候,可以用当前元素的类型接收,也可以用当前元素的父类型接收。

  泛型的细节:

    1)、泛型到底代表什么类型取决于调用者传入的类型,如果没传,默认是Object类型;

    2)、使用带泛型的类创建对象时,等式两边指定的泛型必须一致;

      原因:编译器检查对象调用方法时只看变量,然而程序运行期间调用方法时就要考虑对象具体类型了;

    3)、等式两边可以在任意一边使用泛型,在另一边不使用(考虑向后兼容);

      ArrayList al = new ArrayList

posted on 2021-06-29 10:13  凹凹凸凸  阅读(62)  评论(0编辑  收藏  举报

导航