java基础知识

  • Post author:
  • Post category:java


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

一、Java基础

  • 计算机基础

  • 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的开发和运行环境,java的开发工具和jre。


2,JRE:Java Runtime Environment

:java程序的运行环境,java运行的所需的类库+JVM(java虚拟机)。


3,配置环境变量

:让java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。

环境变量的配置:

①:永久配置方式:JAVA_HOME=%安装路径%\Java\jdk

path=%JAVA_HOME%\bin

②:临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin

特点:系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找。



classpath的配置:

①:永久配置方式:classpath=.;c:\;e:\

②:临时配置方式:set classpath=.;c:\;e:\


注意

:在定义classpath环境变量时,需要注意的情况

如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;

如果指定了classpath,那么会在指定的目录下查找要运行的类文件。

还会在当前目录找吗?两种情况:

①:如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。

②:如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。

一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。


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…};

    /**
     * 二分查找法。必须有前提:数组中的元素要有序。
     * @param arr
     * @param key
     * @return
     */
    public static int halfSeach_2(int[] arr,int key){

      int min,max,mid;
      min = 0;
      max = arr.length—1;
      mid = (max+min)>>1; //(max+min)/2;
      while(arr[mid]!=key){
      if(key>arr[mid]){
        min = mid + 1;
      }
      else if(key<arr[mid])
        max = mid — 1;
        if(max<min)
        return —1;
        mid = (max+min)>>1;
      }
      return mid;
    }

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


面向对象:★★★★★

特点: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帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java

        //格式
    /**   
     *类描述
     *@author 作者名
     *@version 版本号
     */

    /**
     *方法描述
     *@param  参数描述
     *@return  返回值描述

     */

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


设计模式

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

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个。可以通过匿名内部类来完成参数的传递。

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

        //面试

    //1

    new Object(){
      void show(){
        System.out.println("show run");
      }
    }.show();

    //2
    Object obj = new Object(){
      void show(){
        System.out.println("show run");
      }
    };

    obj.show();

1和2的写法正确吗?有区别吗?说出原因。

写法是正确,1和2都是在通过匿名内部类建立一个Object类的子类对象。

区别:

第一个可是编译通过,并运行。

第二个编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了Object类型,而编译时检查Object类中是否有show方法,所以编译失败。

class InnerClassDemo6 {
    +(static)class Inner{
      void show(){}
    }
    public void method(){
      this.new Inner().show();//可以
    }
    public static void main(String[] args) {//static不允许this
      This.new Inner().show();//错误,Inner类需要定义成static
    }
  }

interface Inter{
   void show();
}

class Outer{//通过匿名内部类补足Outer类中的代码。
   public static Inter method(){
      return new Inter(){
        public void show(){}
       };
    }
}


/*
* Outer.method():意思是:Outer中有一个名称为method的方法,而且这个方法是静态的。
* Outer.method().show():当Outer类调用静态的method方法运算结束后的结果又调用了show方法, 
* 意味着:method()方法运算完一个是对象,而且这个对象是Inter类型的。
*/
class InnerClassDemo7 {
   public static void main(String[] args) {
      Outer.method().show();
          function (new Inter(){
         public void show(){}
        }); //匿名内部类作为方法的参数进行传递。
   }
   public static void function(Inter in){
      in.show();
   }
}


异 常:★★★★

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

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

异常体系


java.lang.Throwable:


Throwable:

可抛出的。


Error

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


Exception

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

无论是错误还是异常,它们都有具体的子类体现每一个问题,它们的子类都有一个共性,就是都

以父类名才作为子类的后缀名。


这个体系中的所有类和对象都具备一个独有的特点



就是可抛性


可抛性的体现

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

class  ExceptionDemo{
    public static void main(String[] args) {
            //java.lang.OutOfMemoryError内存溢出错误
         byte[] buf = new byte[1024*1024*700];
    }
}

在开发时,如果定义功能时,发现该功能会出现一些问题,应该

将问题在定义功能时标示出来

,这样调用者就可以在使用这个功能的时候,预先给出处理方式。

如何标示呢?通过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)包括数组、字符串;

2、类型转换异常:ClassCastException

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

4、不支持操作异常;

5、空指针异常(NullPointerException)

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

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




:定义包用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

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


Import — 导入

:类名称变长,写起来很麻烦。为了简化,使用了一个关键字:import,可以使用这个关键字导入指定包中的类。记住:实际开发时,到的哪个类就导入哪个类,不建议使用*.

import packa.*;//这个仅仅是导入了packa当前目录下的所有的类。不包含子包。

import packa.abc.*;//导入了packa包中的子包abc下的当前的所有类。


如果导入的两个包中存在着相同名称的类。这时如果用到该类,必须在代码中指定包名。


常见的软件包:


java.lang : language java的核心包,Object System  String Throwable jdk1.2版本后,该包中的类自动被导入。


java.awt : 定义的都是用于java图形界面开发的对象。


javax.swing: 提供所有的windows桌面应用程序包括的控件,比如:Frame , Dialog, Table, List 等等,就是java的图形界面库。


java.net : 用于java网络编程方面的对象都在该包中。


java.io : input  output 用于操作设备上数据的对象都在该包中。比如:读取硬盘数据,往硬盘写入数据。


java.util : java的工具包,时间对象,集合框架。


java.applet: application+let 客户端java小程序。server+let  ——>  servlet 服务端java小程序。


jar :java的压缩包,主要用于存储类文件,或者配置文件等。


命令格式

:jar –cf 包名.jar 包目录


解压缩

:jar –xvf 包名.jar


将jar包目录列表重定向到一个文件中

:jar –tf 包名.jar >c:\1.txt

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


多线程:★★★★


进程

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


线程

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

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

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接口创建线程。


因为实现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();
         }
      }

   }