目录
一、环境搭建
JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境。SDK是Software Development Kit 一般指软件开发包,可以包括函数库、编译程序等。JDK就是Java Development Kit。JRE是Java Runtime Enviroment是指Java的运行环境,是面向Java程序的使用者,而不是开发者。
JDK是Java Development Kit的缩写,是Java的开发工具包,主要包含了各种类库和工具,当然也包含了另外一个JRE,那么为什么要包含另外一个JRE呢?而且《JDK安装目录》/JRE/bin目录下,包含有server一个文件夹~包含一个jvm.dll,这说明JDK提供了一个虚拟机。另外,JDK的bin目录下有各种Java程序需要用到的命令,与JRE的bin目录最明显的区别就是JDK文件下才有javac,这一点很好理解,因为JRE只是一个运行环境而已,与开发无关。正因为如此,具备开发功能的JDK所包含的JRE下才会同时有server的JVM,而仅仅作为运行环境的JRE下,只需要server的jvm.dll就够了!!!
配置环境变量(将jdk/bin路径添加到系统中,目的:让jdk/bin目录下的文件可以在任意目录下运行)
classpath路径配置(若没配置classpath,java启动jvm后会在当前目录查找要运行的文件;若配置了,会在指定目录下查找)
ps:详细环境变量配置可参考 java基础学习总结——java环境变量配置 - 孤傲苍狼 - 博客园
java运行分为两部分:编译、运行
javac:负责编译,将.java文件编译成JVM能识别的字节码文件,即.class文件;
java:负责运行,启动JVM,加载运行时所需类库,对.class文件进行执行;(一个文件要执行,必须要有一个起点:main函数)
- java的跨平台性:
机器码文件:能被计算机底层(比如说CPU)直接识别并运行的文件;(不同平台,实现不同,即同一个机器码文件,用不同的系统、平台运行,会得到不同的结果)
字节码文件:将java源代码编译后生成的文件;(C、C++编译后直接生成机器码文件)
java编译后生成字节码文件,并通过对应平台的JVM将字节码文件转换成对应平台能执行的机器码文件;(jvm底层实现,不用管如何转换)
- 类加载器
jvm里有多个类加载器,每个类加载器可以负责加载特定位置的类,例如,bootstrap类加载负责加载jre/lib/rt.jar中的类, 我们平时用的jdk中的类都位于rt.jar中。extclassloader负责加载jar/lib/ext/*.jar中的类,appclassloader负责classpath指定的目录或jar中的类。除了bootstrap之外,其他的类加载器本身也都是java类,它们的父类是ClassLoader。
二、java语法基础
变量空间的开辟需要什么要素呢?
1,这个空间要存储什么数据?数据类型。
2,这个空间叫什么名字啊?变量名称。
3,这个空间的第一次的数据是什么? 变量的初始化值。
变量的作用域和生存期:
变量的作用域:
作用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期:
变量从定义的位置开始就在内存中活了;
变量到达它所在的作用域的时候就在内存中消失了;
8中基本数据类型: 整型:long(8),int(4),short(2),byte(1)
浮点型:double(8),float(4)
字符型:char(默认使用unicode作为编码,一个char作为2个字节来存储。如果采用”ISO-8859-1”编码,那么一个char只会有一个字节。如果采用”UTF-8”或者“GB2312”、“GBK”等编码格式呢? 这几种编码格式采用的是动态长度的,如果是英文字符,大家都是一个字节。如果是中文,”UTF-8”是三个字节,而”GBK”和”GB2312”是两个字节。而对于”unicode”而言,无论如何 都是两个字节。)
boolean型(没有具体字节)
ps:String不是基本数据类型,是引用数据类型
自动类型转换:从低级别到高级别,系统自动转的;
强制类型转换:什么情况下使用?把一个高级别的数赋给一个别该数的级别低的变量;
- short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型,所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
补充:String、Integer详解
Java进阶(三十四)Integer与int的种种比较你知道多少?_IT全栈 华强工作室-CSDN博客
int x1 = 5; Integer x2 = 5;
x1 == x2; //true,此处比较会先对x2进行自动拆箱操作,再进行比较
Integer i1 = 127; //自动装箱,编译时会翻译成Integer i1 = Integer.valueOf(127),此时会首先去内存(常量池中)查找有无127常量,若有,将地址赋给i1,若无,新建常量再赋值。所以此处i1指向内存;
Integer i2 = new Integer(127); //此处是在堆内存中创建127常量,i2指向的是堆内存里的地址;
对于valueOf()函数,当常量值在-128~127之间时,会在常量池中对其进行缓存,但值不值这个范围时,不进行缓存。所以:
Integer i3 = 128;
Integer i4 = 128;
i3 == i4; //此处为false;
小结:声明类型为int的变量与声明类型为int或者Integer(会自动拆箱)的变量作比较时,只要数值相同,"=="返回true
由new Integer()创建的两个对象,永远返回false(因为是两个对象,地址不同)
Integer i = 19; 由此种表达式创建的两个变量,值在-128到127之间则"=="返回true,否则返回false。
Java中String两种不同创建方式的区别_h704106603的博客-CSDN博客
Java中String是一个特殊的包装类数据有两种创建形式:
-
String s = "abc";
-
String s = new String("abc");
第一种先在栈中创建一个对String类的对象引用变量s,然后去查找"abc"是否被保存在字符串常量池中,如果没有则在栈中创建三个char型的值'a'、'b'、'c',然后在堆中创建一个String对象object,它的值是刚才在栈中创建的三个char型值组成的数组{'a'、'b'、'c'},接着这个String对象object被存放进字符串常量池,最后将s指向这个对象的地址,如果"abc"已经被保存在字符串常量池中,则在字符串常量池中找到值为"abc"的对象object,然后将s指向这个对象的地址。
第一种特点:JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
第二种可以分解成两步1、String object = "abc"; 2、String s = new String(object); 第一步参考第一种创建方式,而第二步由于"abc"已经被创建并保存到字符串常量池中,因此jvm只会在堆中新创建一个String对象,它的值共享栈中已有的三个char型值。
第二种特点:一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象。
ps:String类对象为不可变字符串,即在此字符串建立后,就不能被改变,想得到不同的字符串,只能新建。看源码可知道原因,因为string对象是由char数组构成,在定义char时,用来final修饰词,值不能被改变。
String与StringBuffer与StringBuilder:
JAVA StringBuffer的用法 - 果维 - 博客园
探秘Java中的String、StringBuilder以及StringBuffer - Matrix海子 - 博客园
string+="hello"的操作事实上会自动被JVM优化成:
StringBuilder str = new StringBuilder(string);·······························
- & 和 &&区别: & :无论左边结果是什么,右边都参与运算。
&&:短路与,如果左边为false,那么右边不参数与运算。
| 和|| 区别:|:两边都运算。
||:短路或,如果左边为true,那么右边不参与运算。
- true&&obj 返回obj
obj&& true 返回true·
false&&obj 与obj&&false都返回false
false&&null 返回false
false&&NaN返回false
NaN&&null 返回NaN
null&&NaN 返回null
switch(变量){
case 值:要执行的语句;break;
…
default:要执行的语句;
}
细节:1):break是可以省略的,如果省略了就一直执行到遇到break为止;
2):switch 后面的小括号中的变量应该是byte,char,short,int,enum,String五种类型中的一种;
3):default可以写在switch结构中的任意位置;如果将default语句放在了第一行,则不管expression与case中的value是否匹配,程序会从default开始执行直到第一个break出现。
4)default语句只有放在所有case最后时 才可省略break(否则和case后的语句一样,遇到break才会结束)
break:作用于switch ,和循环语句,用于跳出当前循环结构,或者称为结束。
break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。
continue:只作用于循环结构,继续循环用的。
作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。
重载:
在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。
如何区分重载:当函数同名时,只看参数列表。和返回值类型没关系。
- 在使用重载要注意以下的几点:
- 在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int));
- 不能通过访问权限、返回类型、抛出的异常进行重载;
- 方法的异常类型和数目不会对重载造成影响;
- 对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。
ps:int max(int a , int b)方法与void max(int a, int b)方法是不构成重载的,现在它们是重名的两个方法,在一个类中声明两个重名的方法是不允许的,编译会出错。
- 重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法。
数 组:用于存储同一类型数据的一个容器。
1)、元素类型[] 变量名 = new 元素类型[元素的个数];
2)、元素类型[] 变量名 = {元素1,元素2...};
java内存简介:
1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。
栈:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );
只要数据运算完成所在的区域结束,该数据就会被释放。
堆:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。
1:每一个实体都有内存首地址值。
2:堆内存中的变量都有默认初始化值。因为数据类型不同,值也不一样。
3:垃圾回收机制。
三、面对对象
匿名对象使用场景:
1:当对方法只进行一次调用的时候,可以使用匿名对象。
2:当对象对成员进行多次调用时,不能使用匿名对象。必须给对象起名字。
在类中定义其实都称之为成员。成员有两种:
1:成员变量:其实对应的就是事物的属性。
2:成员函数:其实对应的就是事物的行为
成员变量和局部变量的区别:
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变量就引用了该实体。(指向了该对象)
Java基础-对象的内存分配与初始化_小划乐的博客-CSDN博客
补充:初始化对象时,静态变量、构造方法等执行顺序
静态代码块、静态变量,构造代码块、实例变量的执行顺序和继承逻辑_Java方向盘-CSDN博客
1.首先,需要明白类的加载顺序。
(1) 父类静态代码块(包括静态初始化块,静态属性,但不包括静态方法)
(2) 子类静态代码块(包括静态初始化块,静态属性,但不包括静态方法 )
(3) 父类非静态代码块( 包括非静态初始化块,非静态属性 )
(4) 父类构造函数
(5) 子类非静态代码块 ( 包括非静态初始化块,非静态属性 )
(6) 子类构造函数
其中:类中静态块按照声明顺序执行,并且(1)和(2)不需要调用new类实例的时候就执行了(意思就是在类加载到方法区的时候执行的) (把类加载到内存时执行,且在main函数之前执行)
2.其次,需要理解子类覆盖父类方法的问题,也就是方法重写实现多态问题。
Base b = new Sub();它为多态的一种表现形式,声明是Base,实现是Sub类, 理解为 b 编译时表现为Base类特性,运行时表现为Sub类特性。
当子类覆盖了父类的方法后,意思是父类的方法已经被重写,题中 父类初始化调用的方法为子类实现的方法,子类实现的方法中调用的baseName为子类中的私有属性。
由1.可知,此时只执行到步骤4.,子类非静态代码块和初始化步骤还没有到,子类中的baseName还没有被初始化。所以此时 baseName为空。 所以为null。
封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
This:代表对象,就是所在函数所属对象的引用。
哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
this 还可以用于构造函数间的调用。
调用格式:this(实际参数);
this对象后面跟上 . 调用的是成员属性和成员方法(一般方法);this对象后面跟上 () 调用的是本类中的对应参数的构造函数。
注意:用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。
static:★★★ 关键字,是一个修饰符,用于修饰类变量和类函数。
注意:
静态方法只能访问静态成员,不可以访问非静态成员。因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。
静态方法中不能使用this,super关键字。因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。
成员变量和静态变量的区别:
1,成员变量所属于对象,所以也称为实例变量。
静态变量所属于类,所以也称为类变量。
2,成员变量存在于堆内存中。
静态变量存在于方法区中。
3,成员变量随着对象创建而存在,随着对象被回收而消失。
静态变量随着类的加载而存在,随着类的消失而消失。
4,成员变量只能被对象所调用。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。
成员变量在类里面声明时如果不进行初始化,那么JAVA会默认给它初始化,而局部变量JAVA不会默认给它初始化,所以在方法里面声明一个局部变量如果不给它初始化时就会出错。
静态的注意:静态的生命周期很长。
静态代码块:就是一个有静态关键字标示的一个代码块区域,定义在类中。作用:可以完成类的初始化,静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行。
Public:访问权限最大。
static:不需要对象,直接类名即可。
void:主函数没有返回值。
Main:主函数特定的名称。
(String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。
jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。
继 承(面向对象特征之一)
- 好处:
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()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。
final特点:
1:这个关键字是一个修饰符,可以修饰类,方法,变量。
2:被final修饰的类是一个最终类,不可以被继承。
3:被final修饰的方法是一个最终方法,不可以被覆盖。
4:被final修饰的变量是一个常量,只能赋值一次。
抽象类: abstract
- 抽象:不具体,看不明白。抽象类表象体现。
在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法。
抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。
- 抽象类的特点:
1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。
2:抽象方法只定义方法声明,并不定义方法实现。
3:抽象类不可以被创建对象(实例化)。
4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。
ps:
- 抽象类不能创建对象,但是也有构造方法(继承的子类可以调用)
- abstract不能用于修饰成员(局部)变量,只能用于修饰类和方法
- 抽象方法和空方法体是不同的概念:
public abstract void test() ;是一个抽象方法,没有方法体;
public void test() { }; 是一个普通方法,已经定义了方法体,只是方法体为空;
- static和abstract不同同时修饰一个方法
因为若用static修饰,即可通过类调用该方法,若再用abstract修饰时,该方法体为空,调用该方法时出错;(static和abstract可以同时修饰内部类)
- abstract修饰的方法不能被定义为private
因为abstract修饰的方法必须被重写才有意义,若被private修饰,则无法被继承,也就无法重写
接口
- 概念
- 特性
接口总结:接口和接口之间可以相互继承,类和类之间可以相互继承,类和接口之间,只能是类来实现接口。
多态
面向对象最核心机制——动态绑定(多态):动态绑定是指在“执行期间”(而非编译期间)判断所引用的实际对象类型,根据其实际的类型调用其相应的方法。所以实际当中找要调用的方法时是动态的去找的,new的是谁就找谁的方法,这就叫动态绑定。动态绑定帮助我们的程序的可扩展性达到了极致。
- 定义:某一类事物的多种存在方式(一个对象,两种形态)
- 对象的多态:父类或者接口的引用指向其子类的对象
- 多态的好处:
提高了代码的扩展性,前期定义的代码可以使用后期的内容
- 多态弊端: 前期定义的内容不能使用(调用)后期子类的特有方法(就是多态调用的只能是父类)。但如果是继承子类覆盖了父类方法,多态调用的仍是子类的方法!
- 多态前提:
1、必须有关系(继承、实现)
2、要有重写
3、父类引用指向子类对象
- 多态实际上是自动类型提升:
Animal a = new cat(); ( cat型自动提升为Animal型(向上转型,得按Animal对象进行操作),因此cat只能调用Anima类中的方法,cat中特有功能无法访问 )。如果还想用猫的特有功能,可以强制向下转型:Cat c = (Cat) a。向下转型是为了使用特有方法。如果两者不具备继承关系不能强制转换!(向上转型为了提高扩展性并限制特有方法,向下转型为了使用特定方法)。对于转型自始至终都是子类对象做着类型的变化:
尽管f是指向Zi()这个对象,但是实际运行依然是Zi()这个对象调用方法。因此会先去方法区Zi()这个空间中去找方法。若ZI()方法区中没有则会去找Fu()方法区,再去堆中找相对应的变量。若没有则在往上找,直到Object类。这其实就是运行时多态,即到底运行Fu()的方法还是Zi()的方法等到程序运行时再去确定。
关于多态更详细内容见:JAVA 多态(运行时多态和编译时多态)及其内存图解_doncoder的博客-CSDN博客_java编译时多态和运行时多态
java基础学习总结——多态(动态绑定) - 孤傲苍狼 - 博客园
四、设计模式
设计模式:解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
1、单例设计模式:
- 解决的问题:保证一个类在内存中的对象唯一性。
比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。
- 思想:
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;
}
}
2、模板方法设计模式
解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
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");
}
}
}
以上内容部分转载自: Java基础知识总结(绝对经典)_xyzopq100的博客-CSDN博客_java知识
五、异常处理
六、数组
- 基本概念:
数组可以看成是多个相同类型数据组合,对这些数据的统一管理。
数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
数组的元素可以是任何数据类型,包括基本类型和引用类型。
C和C++中的数组都可以分配在栈上面,而JAVA中的数组是只能分配在堆上面的,因为JAVA中的数组是引用类型。
- 数组的声明格式有两种:
格式一:数组元素类型 数组名[ ]; 即type var[ ]; int arr[];
格式二:数组元素类型[ ] 数组名; 即type[ ] var; int[] arr;
如:int a[5]; 这样声明一维数组是非法的。
- 数组对象的创建
数组名 = new 数组元素的类型[数组元素的个数]
- 元素为引用数据类型的数组
注意:元素为引用数据类型的数组中的每一个元素都需要实例化。
- 数组的初始化
1、动态初始化
int a[ ]; //定义数组,即声明一个int类型的数组a[ ]
a=new int[3]; //给数组元素分配内存空间。
a[0]=3; a[1]=9; a[2]=8; //给数组元素赋值。
2、静态初始化
int a[ ] = { 3, 9, 8}; //在定义数组的同时给数组分配空间并赋值。
3、默认初始化
数组是引用类型,它的元素相当于类的成员变量,因此给数组分配内存空间后,每个元素也被按照成员变量的规则被隐式初始化。
public class Test{
public static void main(String args[ ]){
int a[ ] = new int[5];
Date[ ] days=new Date[3];
System.out.println(a[3]);
System.out.println(days[2]);
}
}
class Date{
int year, month, day;
Date(int y, int m, int d){
year = y ;
month = m ;
day = d ;
}
}
输出结果:
System.out.println(a[3]); 打印出来的结果是:0。
System.out.println(days[2]); 打印出来的结果是:null(空)
- 二维数组
七、网络编程
八、SSM框架
1、Spring
- AOP概念介绍
所谓AOP,即Aspect orientied program,就是面向方面(切面)的编程。面向切面编程Aspect-Orlented-Programming,即AOP是对面向对象的思维方式的有力补充。AOP的好处是可以动态地添加和删除在切面上的逻辑而不影响原来的执行代码
- 解释什么是方面(切面)
所谓方面(切面),指的是贯穿到系统的各个模块中的系统一个功能就是一个方面(切面),比如,记录日志,统一异常处理,事务处理,权限检查,这些功能都是软件系统的一个面,而不是一点,在各个模块中都要出现。
- 什么是面向方面编程
把系统的一个方面的功能封装成对象的形式来处理就是面向方面(切面)编程
- 怎么进行面向方面编程
把功能模块对应的对象作为切面嵌入到原来的各个系统模块中,采用代理技术,代理会调用目标,同时把切面功能的代码(对象)加入进来。所以,用spring配置代理对象时只要要配两个属性,分别表示目标和切面对象(Advisor)。
2、MVC
- MVC是Model—View—Controler的简称。即模型—视图—控制器。MVC是一种设计模式,它强制性的把应用程序的输入、处理和输出分开。
- MVC中的模型、视图、控制器它们分别担负着不同的任务。
视图: 视图是用户看到并与之交互的界面。视图向用户显示相关的数据,并接受用户的输入。视图不进行任何业务逻辑处理。
模型: 模型表示业务数据和业务处理。相当于JavaBean。一个模型能为多个视图提供数据。这提高了应用程序的重用性
控制器: 当用户单击Web页面中的提交按钮时,控制器接受请求并调用相应的模型去处理请求。然后根据处理的结果调用相应的视图来显示处理的结果。
- MVC的处理过程:首先控制器接受用户的请求,调用相应的模型来进行业务处理,并返回数据给控制器。控制器调用相应的视图来显示处理的结果。并通过视图呈现给用户。
转自:
SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)_在路上-CSDN博客_ssm框架
正在上传…重新上传取消SSM框架原理,作用及使用方法:SSM框架原理,作用及使用方法_bieleyang的博客-CSDN博客_ssm框架
Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析:
Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析 - format丶 - 博客园
九、Object类
- Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入;
- Object类是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。可以使用类型为Object的变量指向任意类型的对象
-
主要方法介绍
package java.lang;
public class Object {
/* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用。*/
private static native void registerNatives();
/* 对象初始化时自动调用此方法*/
static {
registerNatives();
}
/* 返回此 Object 的运行时类。*/
public final native Class<?> getClass();
public native int hashCode();
hashCode()方法:
hash值:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
情景:考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)。
大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值。实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
- 重写hashCode()方法的基本规则:
· 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
· 当两个对象通过equals()方法比较返回true时,则两个对象的hashCode()方法返回相等的值。
· 对象用作equals()方法比较标准的Field,都应该用来计算hashCode值。
Object本地实现的hashCode()方法计算的值是底层代码的实现,采用多种计算参数,返回的并不一定是对象的(虚拟)内存地址,具体取决于运行时库和JVM的具体实现。
1 2 3 |
|
equals()方法:比较两个对象是否相等
我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说使用Object的equals()方法是比较两个对象的内存地址是否相等,即若object1.equals(object2)为true,则表示equals1和equals2实际上是引用同一个对象。虽然有时候Object的equals()方法可以满足我们一些基本的要求,但是我们必须要清楚我们很大部分时间都是进行两个对象的比较,这个时候Object的equals()方法就不可以了,实际上JDK中,String、Math等封装类都对equals()方法进行了重写。
1 |
|
clone()方法:快速创建一个已有对象的副本
第一:Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于Java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息复制到新对象中,虽然这也实现了clone功能。
第二:Object类中的 clone()方法被protected修饰符修饰。这也意味着如果要应用 clone()方 法,必须继承Object类。
第三:Object.clone()方法返回一个Object对象。我们必须进行强制类型转换才能得到我们需要的类型。
克隆的步骤:1:创建一个对象; 2:将原有对象的数据导入到新创建的数据中。
clone方法首先会判对象是否实现了Cloneable接口,若无则抛出CloneNotSupportedException, 最后会调用internalClone. intervalClone是一个native方法,一般来说native方法的执行效率高于非native方法。
复制对象 or 复制引用
1 2 3 4 5 6 7 8 |
|
可已看出,打印的地址值是相同的,既然地址都是相同的,那么肯定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23, "zhang") 。 可以把这种现象叫做引用的复制。
1 2 3 4 5 6 7 8 |
|
从打印结果可以看出,两个对象的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量:
深拷贝 or 浅拷贝
拷贝都需要该类实现cloneable接口
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象(直接指向被拷贝引用对象的地址)。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。
浅拷贝重写clone方法
public Object clone() {
13 Student0 o = null;
14 try {
15 o = (Student0) super.clone();
16 } catch (CloneNotSupportedException e) {
17 System.out.println(e.toString());
18 }
19
20 return o;
21 }
public Object clone() {
13 Student o = null;
14 try {
15 o = (Student) super.clone();
16 } catch (CloneNotSupportedException e) {
17 System.out.println(e.toString());
18 }
19 o.p = (Professor) p.clone();
20 return o;
21 }
深拷贝在重写clone方法时,需在末尾加上对引用对象的拷贝;
(拷贝部分转自java对象克隆以及深拷贝和浅拷贝 - 一生不可自决 - 博客园)
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString():
一个字符串和另外一种类型连接的时候,另外一种类型会自动转换成String类型,然后再和字符串连接。基础的数据类型int,float,double转换成字符串比较简单,按照它们的数字转换过来就成了,可以引用类型呢,Person p = new Person();一个字符串加上这个p,你就不知道要怎么把这个p转换成字符串了,因为这个p是一个引用类型。
任何一个类都是从Object类继承下来的,因此在任何一个类里面都可以重写这个toString()方法。toString()方法的作用是当一个引用对象和字符串作连接的时候,或者是直接打印这个引用对象的时侯,这个引用对象都会自动调用toString()方法,通过这个方法返回一个表示引用对象自己正常信息的字符串,而这个字符串的内容由我们自己去定义,默认的字符串内容是“类名+哈希编码”。因此我们可以通过在类里面重写toString()方法,把默认的字符串内容改成我们自己想要表达的正常信息的字符串内容。
重写toString()方法:
public boolean equals(Object obj){
if (obj==null){
return false;
}
else{
/**
* instanceof是对象运算符。
* 对象运算符用来测定一个对象是否属于某个指定类或指定的子类的实例。
* 对象运算符是一个组合单词instanceof。
* 该运算符是一个双目运算符,其左边的表达式是一个对象,右边的表达式是一个类,
* 如果左边的对象是右边的类创建的对象,则运算结果为true,否则为false。
*/
if (obj instanceof Cat){
Cat c = (Cat)obj;
if (c.color==this.color && c.weight==this.weight && c.height==this.height){
return true;
}
}
}
return false;
}
/*唤醒在此对象监视器上等待的单个线程。*/
public final native void notify();
/*唤醒在此对象监视器上等待的所有线程。*/
public final native void notifyAll();
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/
public final void wait() throws InterruptedException {
wait(0);
}
notify()
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。*/
public final native void wait(long timeout) throws InterruptedException;
/* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。*/
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/
wait(timeout);
}
wait()
/*当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。*/
protected void finalize() throws Throwable { }
}
finalize()
Object类中部分摘自:java.lang包【Object类】 - myseries - 博客园
native关键字
- native是与C++联合开发的时候用的!java自己开发不用的!
- 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。这些函数的实现体在DLL中,JDK的源代码中并不包含,你应该是看不到的。对于不同的平台它们也是不同的。这也是java的底层机制,实际上java就是在不同的平台上调用不同的native方法实现对操作系统的访问的。
- native 是用做java 和其他语言(如c++)进行协作时用的也就是native 后的函数的实现不是用java写的
- native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的,java只能调用。
- java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就需要一些其他语言的帮助,这个就是native的作用了。
- Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。
- 可以将native方法比作Java程序同C程序的接口,其实现步骤:
1、在Java中声明native()方法,然后编译;
2、用javah产生一个.h文件;
3、写一个.cpp文件实现native导出方法,其中需要包含第二步产生的.h文件(注意其中又包含了JDK带的jni.h文件);
4、将第三步的.cpp文件编译成动态链接库文件;
5、在Java中用System.loadLibrary()方法加载第四步产生的动态链接库文件,这个native()方法就可以在Java中被访问了。
十、IO流
1、JAVA流式输入/输出原理
流是用来读写数据的,java有一个类叫File,它封装的是文件的文件名,只是内存里面的一个对象,真正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各样的数据,我们想读文件里面的数据怎么办呢?是通过一个流的方式来读,咱们要想从程序读数据,对于计算机来说,无论读什么类型的数据都是以010101101010这样的形式读取的。怎么把文件里面的数据读出来呢?你可以把文件想象成一个小桶,文件就是一个桶,文件里面的数据就相当于是这个桶里面的水,那么我们怎么从这个桶里面取水呢,也就是怎么从这个文件读取数据呢。
常见的取水的办法是我们用一根管道插到桶上面,然后在管道的另一边打开水龙头,桶里面的水就开始哗啦哗啦地从水龙头里流出来了,桶里面的水是通过这根管道流出来的,因此这根管道就叫流,JAVA里面的流式输入/输出跟水流的原理一模一样,当你要从文件读取数据的时候,一根管道插到文件里面去,然后文件里面的数据就顺着管道流出来,这时你在管道的另一头就可以读取到从文件流出来的各种各样的数据了。当你要往文件写入数据时,也是通过一根管道,让要写入的数据通过这根管道哗啦哗啦地流进文件里面去。除了从文件去取数据以外,还可以通过网络,比如用一根管道把我和你的机子连接起来,我说一句话,通过这个管道流进你的机子里面,你马上就可以看得到,而你说一句话,通过这根管道流到我的机子里面,我也马上就可以看到。有的时候,一根管道不够用,比方说这根管道流过来的水有一些杂质,我们就可以在这个根管道的外面再包一层管道,把杂质给过滤掉。从程序的角度来讲,从计算机读取到的原始数据肯定都是010101这种形式的,一个字节一个字节地往外读,当你这样读的时候你觉得这样的方法不合适,没关系,你再在这根管道的外面再包一层比较强大的管道,这个管道可以把010101帮你转换成字符串。这样你使用程序读取数据时读到的就不再是010101这种形式的数据了,而是一些可以看得懂的字符串了。
2、输入输出流分类
io包里面定义了所有的流,所以一说流指的就是io包里面的
什么叫输入流?什么叫输出流?用一根管道一端插进文件里程序里面,然后开始读数据,那么这是输入还是输出呢?如果站在文件的角度上,这叫输出,如果站在程序的角度上,这叫输入。
记住,以后说输入流和输出流都是站在程序的角度上来说。
3、节点流和处理流
你要是对原始的流不满意,你可以在这根管道外面再套其它的管道,套在其它管道之上的流叫处理流。为什么需要处理流呢?这就跟水流里面有杂质,你要过滤它,你可以再套一层管道过滤这些杂质一样。
3.1.节点流类型
节点流就是一根管道直接插到数据源上面,直接读数据源里面的数据,或者是直接往数据源里面写入数据。典型的节点流是文件流:文件的字节输入流(FileInputStream),文件的字节输出流(FileOutputStream),文件的字符输入流(FileReader),文件的字符输出流(FileWriter)。
3.2.处理流类型
处理流是包在别的流上面的流,相当于是包到别的管道上面的管道。
4、InputStream(输入流)
我们看到的具体的某一些管道,凡是以InputStream结尾的管道,都是以字节的形式向我们的程序输入数据。
4.1.InputStream的基本方法
read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节。read(byte[] buffer)方法读取数据时,先把读取到的数据填满这个byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然后再处理数组里面的数据。这就跟我们取水一样,先用一个桶去接,等桶接满水后再处理桶里面的水。如果是每读取一个字节就处理一个字节,这样子读取也太累了。
更多IO流知识见:java基础学习总结——流 - 孤傲苍狼 - 博客园
十一、线程
1、基本概念
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
进程在执行过程中拥有独立的内存单元,进程有独立的地址空间,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
- 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:
一个指向当前被执行指令的指令指针;
一个栈;
一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
一个私有的数据区。
我们使用Join()方法挂起当前线程,直到调用Join()方法的线程执行完毕。该方法还存在包含参数的重载版本,其中的参数用于指定等待线程结束的最长时间(即超时)所花费的毫秒数。如果线程中的工作在规定的超时时段内结束,该版本的Join()方法将返回一个布尔量True。
简而言之:
一个程序至少有一个进程,一个进程至少有一个线程。线程的划分尺度小于进程,使得多进程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。
- 在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;
2、线程的执行。
在 Java程序中,有三种方法创建线程:
1、是对 Thread 类进行派生并覆盖 run方法;
2、是通过实现Runnable接口创建。
3、实现Callable,重写call()
注意:Callable接口是一个参数化的类型,只有一个call方法,call返回类型是参数类型。
使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
一个Java应用总是从main()方法开始运行,main()方法运行在一个线程内,他被称为主线程。
一旦创建一个新的线程,就产生一个新的调用栈。
线程总体分两类:用户线程和守候线程。
当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。
CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,但实际上在一个时间点上,CPU只有一个线程在运行。在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,因此我们看起来的感觉就像是多线程一样。
什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程。
2、线程的创建
详见:java基础学习总结——线程(一) - 孤傲苍狼 - 博客园
使用实现Runnable接口和继承Thread类这两种开辟新线程的方法的选择应该优先选择实现Runnable接口这种方式去开辟一个新的线程。因为接口的实现可以实现多个,而类的继承只能是单继承。因此在开辟新线程时能够使用Runnable接口就尽量不要使用从Thread类继承的方式来开辟新的线程。
以上所有章节部分摘自:随笔列表第11页 - 孤傲苍狼 - 博客园
文章部分转自:孤傲苍狼 - 博客园