Java基础知识 Java开发环境 Java语言的特性 1.简单性(相对于C++) 在Java语言当中真正操作内存的是JVM(Java Virtual Machine)Java虚拟机
所有的java程序都是运行在Java虚拟机当中的, Java语言屏蔽了指针概念 ,程序员不能直接操作指针 ,或者说程序员不能直接操作内存。这种方式有优点也有缺点:
优点:不容易导致内存泄漏
缺点:效率问题(但是可以通过更换更强大的硬件来解决)
2.完全面向对象 面向对象三大特性: 封装,继承,多态
面向对象更容易让人理解,人类通常是以对象的方式认知世界的。
3.健壮性 Java中有一种机制:自动垃圾回收机制(GC机制 Garbage Collection)
Java 程序启动了一个单独的垃圾回收线程,时刻监测内存使用情况,在特定时机会回收/释放垃圾数据,这样会让内存时刻处于最好的状态, 关于这个机制, 我们会在Java进阶篇中讲解.
相对于C语言来说,java语言是健壮的, 因为Java不容易导致内存的泄漏, C++或者C语言需要程序员自己分配内存, 在使用不当时很容易导致内存泄漏。
内存泄漏: 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。(来自百度百科: 内存泄漏 )
JVM 负责调度GC机制。程序员不需要干涉。
缺点:内存无法得到及时的清理, (但是我们可以手动调用清理机制)
4.java完美支持多线程并发。 5.可移植性/跨平台 java语言只要编写一次,可以做到到处运行。
例如:java程序编写完之后,可以运行在windows操作系统上,不需要做任何改动可以直接运行在Linux操作系统上,同样也可以运行到MaC OS上面。
一次编写,到处运行。(平台改变了,程序不需要改。)
JVM这种机制实现了跨平台,那么这种机制优点和缺点分别是什么?
优点:一次编写到处运行,可以跨平台 。
缺点:麻烦。对于运行java程序来说必须先有一个JVM,也就是需要安装JRE。
JDK、JRE、JVM的区别
JDK(Java Development Kit):Java开发工具箱
JRE(Java Runtime Environment,Java 运行环境):java运行环境
JVM(Java Virtual Machine):java虚拟机
JDK包括JRE,JRE包括JVM。
JVM是不能独立安装的。JRE和JDK都可以独立安装。
安装JDK的时候,JRE就自动安装了,同时JRE内部的JVM也就自动安装了。安装JRE的时候,JVM也就自动安装了。
问题:假设你在软件公司开发了一个新的软件,现在要去客户那边给客户把项目部署一下,把项目跑起来,你需要安装JDK吗?
问题:为什么安装JDK的时候会自带一个JRE?
因为开发完程序之后,要运行这个程序,让这个程序运行起来,需要JRE,所以JDK安装的时候内部自带一个JRE。
Java体系的技术被划分为三大块:
JavaSE:标准版
JavaEE:企业版
JavaME:微型版
对Java的加载与执行的理解 java程序从编写到最终运行经历了哪些过程?
java程序非常重要的两个阶段:
注意:java程序员直接编写的java代码(普通文本)是无法执行被JVM识别的。java程序员编写的java代码这种普通文本必须经过一个编译,将这个“普通文本代码”变成“字节码”,JVM能够识别“字节码”。java代码这种普通文本变成字节码的过程,被称为:编译。(不同的操作系统上编译生成的字节码也是一样的)
java代码这种普通文本被称为:java源代码。(你编写的代码是源代码)源代码不能直接执行,需要先进行编译,生成源代码对应的“字节码”JVM可以识别的是字节码。
编译阶段和运行阶段可以在不同的操作系统上完成吗?
在windows上编译,编译之后生成了“字节码”,把“字节码”放到linux上运行完全可以 ,因为Java是跨平台的。可以做到一次编写到处运行。
java源代码一旦编译之后,源代码可以删除吗?只留下字节码可以执行吗?
完全可以 执行,因为源代码不参与程序的执行过程 。参与程序执行过程的是字节码。但是最好不要删除源代码。因为有可能执行结果不是你需要的,当执行结果不是你需要的时候,你可以重新打开源代码进行修改,然后重新编译生成新的字节码,再重新执行。这样会有新的执行效果。
放源代码的文件扩展名必须是:xxx.java
并且需要注意的是:编译生成的字节码文件扩展名是:xxx.class
.java文件就是源文件,这个文件中编写源代码。
.class文件就是字节码文件,这个文件是编译源代码而得到的。
1个java源文件是可以编译生成多个class文件的。
最终运行的是class文件。
问题:字节码文件是二进制文件吗?
字节码文件不是二进制文件。如果是二进制的话,就不需要JVM了。因为操作系统可以直接执行二进制。
java程序从开发到最终运行经历了什么?
编译期:(可以在windows上)
第一步:在硬盘的某个位置(随意),新建一个xxx.java文件
第二步:使用记事本或者其它文本编辑器例如EditPlus打开xxx.java文件
第三步:在xxx.java文件中编写“符合java语法规则的”源代码。
第四步:保存(一定要将xxx.java文件保存一下)
第五步:使用编译器(javac【JDK安装后自带】)对xxx.java文件进行编译。
第六步:如果xxx.java文件中编写的源代码是符合语法规则的,编译会通过,如果xxx.java文件中编写的源代码违背了语法规则,那么编译器会报错,编译器报错之后class文件是不会生成的,只有编译通过了才会生成class字节码文件。并且一个java源文件是可以生成多个class文件的。(编译实质上是检查语法)
运行期(JRE在起作用):(可以在windows上,也可以在其他的OS上。)
第七步:如果是在Linux上运行,需要将windows上生成的class文件拷贝过去不需要拷贝源代码,真正运行的是字节码。(但是源代码也不要删除,有用)
第八步:使用JDK自带的一个命令/工具:java(负责运行的命令/工具)执行字节码
第九步:往下的步骤就全部交给JVM了,就不需要程序员干涉了。JVM会将字节码文件装载进去,然后JVM对字节码进行解释(解释器负责将字节码解释为1010101010..等的二进制)
第十步:JVM会将生成的二进制码交给OS操作系统,操作系统会执行二进制码和 硬件进行交互。
注意:在以上的过程中,需要使用两个非常重要的命令?
javac 命令,负责编译
java 命令,负责运行
xxx.java源文件经过编译之后生成了A.class、B.class、C.class等文件,那么我们称A是一个类、B是一个类、C是一个类。其中A、B、C是类的名字。
源文件中编写的代码叫做:源代码。
以上是一个复杂的过程,那么缩减一下,程序员到底要干啥?
新建java文件
打开java文件
写java源代码
保存
javac命令编译
java命令运行
编写、编译、运行
Hello World 第一步:安装文本编辑器
第二步:安装JDK(先下载JDK)安装JDK13,直接下一步就行。
JDK13安装的时候内置了一个JRE,独立于JDK之外的JRE并没有生成。对于java13来说,如果你希望生成一个独立于JDK之外的JRE的话需要执行特殊的命令。
注意: JDK8安装的时候,不仅JDK内置了一个JRE,而且还会在JDK目录之外独立的生成一个单独的JRE。(以前低版本的时候,JRE实际上是有2个。)一个是JDK内置的,一个是独立于JDK之外的。
JDK的bin目录下有:
javac.exe 负责编译
java.exe 负责运行
第三步:写代码
第四步:编译
第五步:运行
编译 怎么编译?使用什么命令?这个命令怎么用?
需要使用的命令是:C:\Program Files\Java\jdk-13.0.2\bin\javac.exe
这个命令需要先测试一下,打开DOS命令窗口,看看javac命令是否可用。
C:\Users\Administrator>javac
‘javac’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
这说明:windows操作系统没有发现“javac.exe”命令在哪里。windows操作系统没有找到javac.exe文件在哪。
为什么ipconfig、ping等命令可以使用呢?为什么javac用不了?
我们发现windows操作系统中有这样一个环境变量,名字叫做:path,并且发现path环境变量的值是:C:\Windows\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\我们还发现了在:C:\Windows\System32 这个目录下存在:ipconfig.exe
注意:修改完环境变量之后,DOS命令窗口必须关闭重新打开才会起作用。
将path环境变量中的:C:\windows\system32; 删除之后再测试::\Users\Administrator>ipconfig ‘ipconfig’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
配置环境变量path的步骤:
桌面计算机上右键–>属性–>高级系统设置–>环境变量
怎么修改path环境变量?
找到path,鼠标双击!path环境变量当中都是路径,路径和路径之间必须采用“半角的分号”分隔。
让javac.exe能用,我们配置哪个路径到path中?将C:\Program Files\Java\jdk-13.0.2\bin配置到path当中。
注意:
环境变量包括“系统变量”和“用户变量”
系统变量:范围比较大,系统变量会让计算机所有用户都起作用。
用户变量:范围比较小,这个变量只是作用于当前用户。
怎么查看编译器版本?
1 2 C:\Users\Administrator><u>javac -version</u> javac 13.0.2
怎么查看java虚拟机的版本?
1 2 3 4 C:\Users\Administrator><u>java -version</u> java version "13.0.2" 2020-01-14 Java(TM) SE Runtime Environment (build 13.0.2+8) Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
问题1 :path环境变量的作用是什么?
path环境变量的作用就是给windows操作系统指路的。告诉windows操作系统去哪里找这个命令文件。 path环境变量中有很多很多的路径,路径和路径之间用半角分号分隔。
path=A;B;C;D……path是环境变量的名字。A;B;C;D……是环境变量path的值。
问题2 :path环境变量是java中的机制,还是windows操作系统中的机制?path环境变量是隶属于java的吗?path环境变量和java有关系吗?
path环境变量本质上是隶属于windows操作系统的,和java没有关系。java只不过用了一下path环境变量。
运行 运行的前提是:class文件(字节码)生成了。没有字节码文件程序是无法运行的。
假设该文件的名字叫做:HelloWorld.class
那么HelloWorld被称为类名;如果文件名是Test.class,那么Test就是一个类名。
怎么运行,使用哪个命令?使用JDK的bin目录下的:java.exe命令来运行。
先在DOS命令窗口中测试java.exe这个命令是否可用!java -version
“java.exe”这个命令怎么用,语法格式是什么?java 类名
java HelloWorld.class 对不对?不对!正确的写法是:java HelloWorld
千万千万要注意:java这个命令,后面跟的是“类名”,而绝对不能跟“文件路径”,因为java命令后面跟的不是文件,是一个“类名”。
对于这个类:Test.class ,应该:java Test
java HelloWorld”的执行过程以及原理 D:\course\JavaProjects\02-JavaSE\chapter01>java HelloWorld 敲完回车,都发生了什么?????
第一步:
会先启动JVM (java虚拟机)
第二步:
JVM启动之后,JVM会去启动“类加载器classloader”
类加载器的作用:
加载类的。本质上类加载器负责去硬盘上找“类”对应的“字节码”文件 。
假设是“java HelloWorld”,那么类加载器会去硬盘上搜索:HelloWorld.class文件。
假设是“java Test”,那么类加载器会去硬盘上搜索:Test.class文件。 …….
第三步:
类加载器如果在硬盘上找不到对应的字节码文件,会报错,报什么错?
类加载器如果在硬盘上找到了对应的字节码文件,类加载器会将该字节码文件装载到JVM当中,JVM启动“解释器”将字节码解释为“101010000…”这种二进制码,操作系统执行二进制码和硬件交互。
默认情况下,类加载器去硬盘上找“字节码”文件的时候,默认从哪找?
默认情况下类加载器(classloader)会从当前路径下找。
能不能给类加载器指定一个路径,让类加载器去指定的路径下加载字节码文件?
答案:可以的。但是我们需要设置一个环境变量,叫做:classpath
classpath是一个环境变量,是干啥的?
classpath环境变量不属于 windows操作系统,classpath环境变量隶属于java
classpath环境变量是java特有的。
classpath=A路径;B路径;C路径…..
classpath是一个变量名,A路径;B路径;C路径…..是变量值
我们把classpath配置一下,这个环境变量在windows中没有,需要新建。
计算机-->右键-->属性-->高级系统设置-->环境变量-->新建...
注意:变量名不能随意写:大小写无所谓,但必须叫做:classpath
我目前是随意配置的:(重启CMD)classpath=D:\course
非常重要的一个特点,必须记住:配置了classpath=D:\course之后,类加载器只会去D:\course目录下找“xxx.class”文件,不再从当前路径下找了。
到目前为止:classpath环境变量不需要配置。但你必须理解classpath环境变量是干什么的!
你一定要理解classpath环境变量的作用是什么?
是给类加载器指路的。
在没有配置环境变量classpath的时候,默认从当前路径下加载。
如果配置了环境变量classpath的话,就只能从指定的路径下加载了。
path java_home classpath,这3个环境变量path需要配置,后面两个暂时不配置。
在高版本的JDK当中,有这样的一个新特性,可以直接这样一步到位:
java x/y/z/xxx.java
java后面直接加java源文件的路径。
这个特性是为了简化开发而提出,但实际上底层的实现原理还是和以前一样的,以上命令在执行过程中,还是会先进行编译,然后再运行。并且以上的运行方式,编译生成的class文件在硬盘上不存在,看不到。
对于第一个程序的解释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 public class HelloWorld { public static void main (String[] args) { System.out.print("Hello " ); System.out.println("World!" ); } } public class Test2 {} public class Test3 { static void main (String[] args) { } } public class Test4 { public static void main (String[] fdsafdsafdsafdsa) { System.out.println("hello world" ); } } public class Test5 { System.out.println("hello1" ); public static void main (String[] args) { } System.out.println("hello2" ); } public class Test6 { public static void main (String[] args) { } } public class Test7 { public static void main (String[] args) { System.out.println(100 ); System.out.println("100" ); System.out.println(100 + 200 ); System.out.println(200 - 100 ); System.out.println(200 * 100 ); System.out.println(200 / 100 ); } } class A {} class B {} class C {} class D {} class A {} public class Test8 {} class C {} class D {} class T1 { public static void main (String[] args) { System.out.println("T1....." ); } } class T2 { public static void main (String[] args) { System.out.println("T2....." ); } }
Tips
终止程序用System.exit() 方法
函数调用:object.method(parameters)
标识符与关键字 标识符 规则1:标识符只能由数字、字母(包括中文)、下划线_、美元符号$组成,不能含有其它符号(比如空格)。
规则2:标识符不能以数字开头
规则3:关键字不能做标识符。例如:public class static void 这些蓝色的字体都是关键字,关键字是不能做标识符的。
规则4:标识符是严格区分大小写的。大写A和小写a不一样。
规则5:标识符理论上是没有长度限制的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BiaoShiFuTest { public static void main (String[] args) { } public static void doSome () { int k = 100 ; int nianLing = 20 ; } }
以上代码编译报错,错误信息是:
解决办法:将123ABC修改为合法的标识符。
类名是标识符,标识符“中”不能有空格
编译器错误信息是:
错误: 需要’{‘
编译器检测到class,然后找class后面的标识符,编译器找到了一个合法的标识符叫做“Hello”,然后编译器继续往后找“{”,结果没有找到“{”,所以报错了。
解决办法:
1 2 3 4 5 6 7 8 9 10 11 class Hello {} class HelloWorld {} class _A {} class _$1Aa 你{}
题目:
创建一个java文件,起名 123.java可以吗?
可以,完全可以,在windows操作系统中文件名叫做:123.java 没毛病。
123其实并不是标识符。只是一个文件名。
只不过在123.java文件中无法定义public的类。
标识符除了命名规则之外,还有命名规范:
1、命名规则和命名规范有什么区别?
规则类似于现实世界中的法律。规范类似于现实世界中的道德。
统一按照规范进行的话,代码的可读性很好。 代码很容易让其它开发人员理解。
2、具体的命名规范是哪些?
规范1:见名知意(这个标识符在起名的时候,最好一看这个单词就知道啥意思。)
规范2:遵循驼峰命名方式,什么是驼峰(一高一低,一高一低…)驼峰有利于单词与单词之间很好的进行分隔, BiaoShiFuTest,这个很好,一眼就能看出来是4个单词。
规范3:类名、接口名有特殊要求: 类名和接口名首字母大写,后面每个单词首字母大写。StudentTest、UserTest ,这是类名、接口名。
规范4:变量名、方法名有特殊要求。
变量名和方法名首字母小写,后面每个单词首字母大写。nianLing(NianLing这样就不符合了。)mingZi(MingZi这样也不符合了。)
规范5:所有“常量”名:全部大写,并且单词和单词之间采用下划线衔接。
1 2 3 4 5 6 7 8 public class IdentifierTest { public static void main (String[] args) { int nianLing = 20 ; String mingZi = "zhangsan" ; } }
关键字 什么是关键字?在SUN公司开发Java语言的时候,提前定义好了一些具有特殊含义的单词,这些单词全部小写,具有特殊含义,不能用作标识符。
切记:java语言中的所有关键字都是全部小写。注意:java语言中是严格区分大小写的。public和Public不一样。Class和class不一样。static和Static也不一样。
Tips
java 中整型的范围与运行代码的机器无关
从java7开始,加上前缀0b或者0B就可以写二进制数,比如0b10100
还可以为数字加上下划线,如1_0000_0000,等价于100000000
在C/C++中,int和long等类型的大小与平台直接相关,int型在16位的平台上占用2字节的大小,在32位的平台上占用4字节的大小,在Java中,占用的大小是固定的,与平台无关。
Java中没有任何的无符号整型
Java无法精确打印出2.0-1.1的结果的原因(0.89999999而不是0.9)
浮点数采用二进制数表示,但是二进制系统中无法精确的表示分数1/10
强烈建议不要使用char类型,除非确实需要处理UTF-16的代码单元
不提倡在一行中声明多个变量,逐一声明变量可以提高程序的可读性
变量与数据类型 变量 字面量:字面量就是数据、数据就是字面量、是一个东西。在java语言中“数据”被称为“字面量”。
1 2 3 4 5 6 7 10 100 123 :整型 1.34 3.14 2.0:浮点型 true false :布尔型 'a' '国':字符型 "a" "abc" "国" "中国":字符串型 10:整数,是一个数字 "10":它不是数字,是一个字符串,或者说,它属于“文字类”的。性质完全不同,在计算机中的对应的二进制码也是完全不同的。
其中字符型和字符串型都是描述了现实世界中的文字,需要注意的是:
所有的字符型只能使用单引号括起来。
所有的字符串型只能使用双引号括起来。
字符型一定是单个字符才能成为“字符型”
在语法级别上怎么区分字符型和字符串型?
主要看是双引号还是单引号。
单引号的一定是字符型。
双引号的一定是字符串型。
什么是变量?
变量就是一个存数据盒子。在内存中的最基本的存储单元。是存数据用的,而且这个数据是可变的,所以叫做变量。(盒子大小谁来决定?数据类型)
变量的使用
变量的三要素?
数据类型、变量名、值 (值就是数据,就是字面量。)int i = 100;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int k; System.out.println(k); int a, b, c = 100 ;int a = 10 , b = 20 , c = 100 ;int i;i = 100 ; int k = 200 ;
在“同一个域”当中,变量名不能重名!但可以重新赋值
1 2 3 4 5 { int i = 100 ; i = 300 ; }
什么叫做同一个域?一个大括号代表一个域。
变量的分类
根据位置进行分类:记住就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static void m1 () { int k = 100 ; int i = 200 ; } public class T {public static void x () {} int i = 200 ; } { int i = 100 ; { } } { }
变量的作用域?
1、什么是作用域?变量的有效范围。
2、关于变量的作用域,可一句话:出了大括号就不认识了。(死记这句话。)
3、java中有一个很重要的原则:就近原则。(不仅java中是这样,其它编程语言都有这个原则。)哪个离我近,就访问哪个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class VarTest08 { int i = 10000 ; public static void main (String[] args) { int i = 100 ; System.out.println(i); for (int n = 0 ; n < 10 ; n++){ } int k; for (k = 0 ; k < 10 ; k++){ } } public static void x () { int i = 200 ; } }
数据类型 数据类型有什么用?
数据类型用来声明变量,程序在运行过程中根据不同的数据类型分配不同大小的空间。
数据类型在java语言中包括两种:
第一种:基本数据类型(8小种)基本数据类型又可以划分为4大类8小种:
第一类:整数型 byte,short,int,long (没有小数的)
第二类:浮点型 float,double (带有小数的)
第三类:布尔型 boolean:只有两个值true和false,true表示真,false表示假
第四类:字符型 char:java中规定字符型字面量必须使用单引号括起来。属于文字。
第二种:引用数据类型
字符串型String属于引用数据类型。String字符串不属于基本数据类型范畴。
java中除了基本数据类型之外,剩下的都是引用数据类型。引用数据类型后期面向对象的时候才会接触。
整数型:byte short int long有什么区别?
浮点型:float和double有什么区别?
区别:占用的空间大小不同。
类型
占用字节数量(byte)
取值范围
byte
1
-128 ~ 127
short
2
-32768 ~ 32767
int
4
-2147483648 ~ 2147483647
long
8
-2^63~2^63-1
float
4
-2^31~2^31-1
double
8
-2^63~2^63-1
boolean
1
true、false
char
2
0 ~ 65535
short和char实际上容量相同,不过char可以表示更大的数字。
因为char表示的是文字,文件没有正负之分,所以char可以表示更大的数字。
其中byte,short,int,long,float,double,boolean,这7种类型计算机表示起来比较容易,因为他们都是数字。其中布尔类型只有两个值true和false,实际上true和false分别在C++中对应的是1和0,1为true,false为0;对于char类型来说计算机表示起来比较麻烦,因为char对应的是文字,每一个国家的文字不一样,文字不能直接通过“自然算法”转换成二进制。这个时候怎么办?字符编码诞生了。
什么是字符编码?
字符编码是人为的定义的一套转换表。在字符编码中规定了一系列的文字对应的二进制。字符编码其实本质上就是一本字典,该字段中描述了文字与二进制之间的对照关系。字符编码是人为规定的。
字符编码涉及到编码和解码两个过程,编码和解码的时候必须采用同一套字符编码方式,不然就会出现乱码。
关于字符编码的发展过程?
起初的时候计算机是不支持文字的,只支持科学计算。实际上计算机起初是为了战争而开发的,计算导弹的轨道….
后来随着计算机的发展,计算机开始支持文字,最先支持的文字是英文,英文对应的字符编码方式是:ASCII码。
在java中,java语言为了支持全球所有的文字,采用了一种字符编码方式叫做unicode编码。unicode编码统一了全球所有的文字,支持所有文字。具体的实现包括:UTF-8 UTF-16 UTF-32….
ASCII(’a’是97 ‘A’是65 ‘0’是48…)
字符型:char 1、char占用2个字节。
2、char的取值范围:[0-65535]
3、char采用unicode编码方式。
4、char类型的字面量使用单引号括起来。
5、char可以存储一个汉字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class CharTest01 { public static void main (String[] args) { char c1 = '中' ; System.out.println(c1); char c2 = 'a' ; System.out.println(c2); char c3 = '0' ; System.out.println(c3); } }
转义字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class CharTest02 { public static void main (String[] args) { System.out.println("s" ); System.out.println("'" ); System.out.println("'这样呢'" ); char x = '\u4e2d' ; System.out.println(x); } } public class CharTest03 { public static void main (String[] args) { char c1 = 'a' ; System.out.println(c1); char c2 = 97 ; System.out.println(c2); char c3 = 65535 ; System.out.println(c3); char c4 = (char )65536 ; byte x = 1 ; short s = 1 ; char c = 1 ; } }
整数型:byte short int long byte b = 127; // 可以直接赋值short s = 32767; // 可以直接赋值
char // 没有超出char的取值范围可以直接赋值给char变量吗?
浮点型:float double
布尔型:boolean
1 2 3 4 5 6 7 8 9 10 11 12 13 public class IntTest01 { public static void main (String[] args) { int d = 0b10 ; System.out.println(d); } }
过大整数的问题 在java中有一条非常重要的结论,必须记住:
在任何情况下,整数型的“字面量/数据”默认被当做int类型处理。(记住就行)果希望该“整数型字面量”被当做long类型来处理,需要在“字面量”后面添加L/l, 建议使用大写L,因为小写l和1分不清。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class IntTest02 { public static void main (String[] args) { int a = 100 ; System.out.println(a); long b = 200 ; System.out.println(b); long c = 300L ; System.out.println(c); long d = 2147483647 ; System.out.println(d); long e = 2147483648L ; System.out.println(e); } }
自动类型转换和强制类型转换 1、小容量可以直接赋值给大容量,称为自动类型转换。
2、大容量不能直接赋值给小容量,需要使用强制类型转换符进行强转。但需要注意的是:加强制类型转换符之后,虽然编译通过了,但是运行的时候可能会损失精度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class IntTest03 { public static void main (String[] args) { long x = 100L ; int y = (int )x; System.out.println(y); int a = 100 ; System.out.println(a); int b = a; System.out.println(b); } }
强制类型转换的精度损失 java中有一个语法规则:
当这个整数型字面量没有超出byte的取值范围,那么这个整数型字面量可以直接赋值给byte类型的变量。short 同理。
这种语法机制是为了方便写代码,而存在的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class IntTest04 { public static void main (String[] args) { byte b = (byte )300 ; System.out.println(b); byte x = 1 ; byte y = 127 ; byte z = 128 ; short s = 1 ; short s1 = 32767 ; System.out.println(s); } }
byte, char, short 混合运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class IntTest06 { public static void main (String[] args) { char c1 = 'a' ; byte b = 1 ; System.out.println(c1 + b); short s = (short )(c1 + b); int a = 1 ; short x = a; System.out.println(x); } }
多种类型混合运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class IntTest07 { public static void main (String[] args) { long a = 10L ; char c = 'a' ; short s = 100 ; int i = 30 ; System.out.println(a + c + s + i); int x = (int )(a + c + s + i); System.out.println(x); int temp = 10 / 3 ; System.out.println(temp); int temp2 = 1 / 2 ; System.out.println(temp2); } }
浮点型数据 关于java语言中的浮点型数据
浮点型包括:
类型
大小
解释
float
4个字节
float是单精度
double
8个字节
double是双精度,更精确
比如说:
但是需要注意的是,如果用在银行方面或者说使用在财务方面,double也是远远不够的,在java中提供了一种精度更高的类型,这种类型专门使用在财务软件方面:java.math.BigDecimal (不是基本数据类型,属于引用数据类型。)
float和double存储数据的时候都是存储的近似值。
因为现实世界中有这种无限循环的数据,例如:3.3333333333333….数据实际上是无限循环,但是计算机的内存有限,用一个有限的资源表示无限的数据,只能存储近似值。
long类型占用8个字节。float类型占用4个字节。
哪个容量大?
注意:任意一个浮点型都比整数型空间大。float容量 > long容量。
java中规定,任何一个浮点型数据默认被当做double来处理。如果想让这个浮点型字面量被当做float类型来处理,那么请在字面量后面添加F/f。
1.0 那么1.0默认被当做double类型处理。 1.0F 这才是float类型。(1.0f)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class FloatTest01 { public static void main (String[] args) { double pi = 3.1415926 ; System.out.println(pi); float f = (float )3.14 ; System.out.println(f); int i = (int )10.0 / 5 ; System.out.println(i); int x = (int )(10.0 / 5 ); System.out.println(x); } }
布尔类型 1、在java语言中boolean类型只有两个值,没有其他值:true和false。不像C或者C++,C语言中1和0也可以表示布尔类型。
2、boolean类型在实际开发中使用在哪里呢? 使用在逻辑判断当中,通常放到条件的位置上(充当条件)
1 2 3 4 5 6 public class BooleanTest01 { public static void main (String[] args) { } }
类型转换 在类型转换的时候需要遵循哪些规则?
第一条:八种基本数据类型中,除 boolean 类型不能转换,剩下七种类型之间都可以进行转换;
第二条:如果整数型字面量没有超出 byte,short,char 的取值范围,可以直接将其赋值给byte,short,char 类型的变量;
第三条:小容量向大容量转换称为自动类型转换,容量从小到大的排序为:byte < short(char) < int < long < float < double,其中 short和 char都占用两个字节,但是char 可以表示更大的正整数;
第四条:大容量转换成小容量,称为强制类型转换,编写时必须添加“强制类型转换符”,但运行时可能出现精度损失,谨慎使用;
第五条:byte,short,char 类型混合运算时,先各自转换成 int 类型再做运算;
第六条:多种数据类型混合运算,各自先转换成容量最大的那一种再做运算;
运算符
逻辑运算符 逻辑运算符:
true & true –> true
非常重要:逻辑运算符两边要求都是布尔类型,并且最终的运算结果也是布尔类型。这是逻辑运算符的特点。
100 & true 不行,语法错误。
100 & 200 不行,没有这种语法。
true & false 这样可以。
100 > 90 & 100 > 101 –> false
& 两边都是true,结果才是true
| 有一边是true,结果就是true
短路与&& 和 逻辑与 &有什么区别?
首先这两个运算符的运算结果没有任何区别,完全相同。只不过“短路与&&”会发生短路现象。
什么是短路现象呢?
什么时候使用&&,什么时候使用& ?
从效率方面来说,&&比&的效率高一些。因为逻辑与&不管第一个表达式结果是什么,第二个表达式一定会执行。
以后的开发中,短路与&&和逻辑与还是需要同时并存的。
大部分情况下都建议使用短路与&&,只有当既需要左边表达式执行,又需要右边表达式执行的时候,才会选择逻辑与&。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 System.out.println(true & true ); System.out.println(true & false ); System.out.println(false & false ); System.out.println(true && true ); System.out.println(true && false ); System.out.println(false && false ); int x = 10 ;int y = 11 ;System.out.println(x > y & x > y++); System.out.println(y); int m = 10 ;int n = 11 ;System.out.println(m > n && m > n++); System.out.println(n);
问题:什么时候发生短路或现象?|| 短路或
“或”的时候只要有一边是true,结果就是true。
所以,当左边的表达式结果是true的时候,右边的表达式不需要执行,此时会短路。
赋值运算符 扩展的赋值运算符?
使用扩展赋值运算符的时候,永远都不会改变运算结果类型。
byte x = 100;x += 1;
x自诞生以来是byte类型,那么x变量的类型永远都是byte。不会变。不管后面是多大的数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class OperatorTest04 { public static void main (String[] args) { byte x = 100 ; System.out.println(x); x += 1 ; System.out.println(x); x += 199 ; System.out.println(x); int y = 100 ; y += 100 ; System.out.println(y); y -= 100 ; System.out.println(y); y *= 10 ; System.out.println(y); y /= 30 ; System.out.println(y); y %= 10 ; System.out.println(y); } }
一个特例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Homework01 { public static void main (String[] args) { int i = 10 ; i = i++; System.out.println(i); int k = 10 ; int temp = k; k++; k = temp; System.out.println(k); } }
字符串链接运算符
1、+ 运算符在java语言中有两个作用。
2、什么时候求和?什么时候进行字符串的拼接呢?
3、一定要记住:字符串拼接完之后的结果还是一个字符串。
接收用户键盘输入 输出信息到控制台:System.out.println(...);
2、在java中怎么接收键盘的输入呢?
java.util.Scanner s = new java.util.Scanner(System.in);
接收一个整数 int num = s.nextInt();
接收一个字符串 String str = s.next();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class KeyInput { public static void main (String[] args) { java.util.Scanner s = new java .util.Scanner(System.in); int i = s.nextInt(); System.out.println("您输入的数字是:" + i); int j = s.nextInt(); System.out.println("您输入的数字是:" + j); int m = s.nextInt(); System.out.println("您输入的数字是:" + m); String str = s.next(); System.out.println("您输入了:" + str); System.out.print("请输入用户名:" ); String name = s.next(); System.out.println("欢迎" +name+"回来" ); } }
Tips
在C++中,可以在嵌套的块中重定义一个变量,在内层定义的变量会覆盖在外层的变量,这样可能会导致程序设计的错误,所以在Java中不能这样做。
Java的引用相当于C++的指针而不是引用,因为:
java中的null引用相当与C++中的NULL指针
控制语句和方法 控制语句
选择语句
if
switch
switch语句本质上是只支持int和String,但是byte,short,char也可以使用在switch语句当中,因为byte short char可以进行自动类型转换 。
循环语句
转向语句
记住 :Java的 int 和 boolean 不能转换 !!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class BreakTest01 { public static void main (String[] args) { a:for (int k = 0 ; k < 2 ; k++){ b:for (int i = 0 ; i < 10 ; i++){ if (i == 5 ){ break a; } System.out.println("i ===> " + i); } } System.out.println("呵呵" ); } }
方法 方法定义的先后顺序没有关系。都可以。
方法怎么定义,语法机制是什么?
1 2 3 [修饰符列表] 返回值类型 方法名(形式参数列表){ 方法体; }
注意:
关于修饰符列表:
修饰符列表不是必选项,是可选的。目前为止,大家统一写成:public static
关于返回值类型:
第一:返回值类型可以是任何类型,只要是java中合法的数据类型就行,数据类型包括基本数据类型和引用数据类型,也就是说返回值类型可以是:byte short int long float double boolean char String……
第二:方法就是为了完成某个特定的功能,方法结束之后大部分情况下都是有一个结果的,而体现结果的一般都是数据。数据得有类型。这就是返回值类型。方法执行结束之后的返回值实际上是给调用者了。谁调用就返回给谁。
第三:当一个方法执行结束不返回任何值的时候,返回值类型也不能空白,必须写上void关键字。所以void表示该方法执行结束后不返回任何结果。
第四:如果返回值类型“不是void”,那么你在方法体执行结束的时候必须使用”return 值;”这样的语句来完成“值”的返回,如果没有“return 值;”这样的语句那么编译器会报错。return 值; 这样的语句作用是什么?作用是“返回值”,返回方法的执行结果。
第五:只要有“return”关键字的语句执行,当前方法必然结束。return只要执行,当前所在的方法结束,记住:不是整个程序结束。
第六:如果返回值类型是void,那么在方法体当中不能有“return 值;”这样的语句。但是可以有“return;”语句。这个语句“return;”的作用就是用来终止当前方法的。
第七:除了void之外,剩下的都必须有“return 值;”这样的语句。
形式参数列表
简称:形参
注意:形式参数列表中的每一个参数都是“局部变量”,方法结束之后内存释放。
形参的个数是:0~N个。
public static void sumInt(){}
public static void sumInt(int x){}
形参有多个的话使用“逗号,”隔开。逗号是英文的。形参的数据类型起决定性作用,形参对应的变量名是随意的。
方法体:
怎么调用呢
类名.方法名(实际参数列表);
实参和形参的类型必须一一对应,个数也要一一对应。
你定义了一个/抽取了一个方法出来,而这个方法确无法完成某个功能,那么你抽取的这个方法毫无意义。般一个方法就是一个“功能单元”。假设在以后的开发中,某个功能是可以独立抽取出来的,建议定义为方法,这样以后只要需要这个功能,那么直接调用这个方法即可,而不需要重复编写业务逻辑代码。
“类名.”是可以省略
a()方法调用b()方法的时候,a和b方法都在同一个类中,“类名.”可以省略。如果不在同一个类中“类名.”不能省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static int m () { boolean flag = true ; if (flag){ return 1 ; } return 0 ; }
重载 注意:如果功能不相似,坚决要让方法名不一致。
重载条件
条件1:在同一个类当中
条件2:方法名相同
条件3:形式参数列表不同(类型、个数、顺序)
注意:方法重载和返回值类型无关,和修饰符列表无关。
在java语言中,是怎么进行方法区分的?
首先java编译器会通过方法名 进行区分。
但是在java语言中允许方法名相同的情况出现。
如果方法名相同的情况下,编译器会通过方法的参数类型 进行方法的区分。
递归
方法自身调用自身。
使用递归的时候,必须添加结束条件,没有结束条件,会发生栈内存溢出错误。
StackOverflowError 原因:一直压栈,没有弹栈,栈内存不够用。
能够使用循环代替递归的尽量使用循环,循环的执行耗费内存少一些,递归耗费内存相对多一些,另外递归使用不当很容易内存溢出,JVM停止工作。JVM发生错误之后只有一个结果,就是退出JVM。当然,只有极少数情况下,只能用递归,其它代码解决不了问题。
当递归有结束条件,并且结束条件合法的时候,就一定不会内存溢出吗? 也不一定。可能递归的太深了。栈内存不够了。因为一直在压栈。
在实际的开发中,不建议轻易的选择递归,能用for循环while循环代替的,尽量使用循环来做。因为循环的效率高,耗费的内存少。递归耗费的内存比较大,另外递归的使用不当,会导致JVM死掉。(但在极少数的情况下,不用递归,这个程序没法实现。)所以:递归我们还是要认真学习的。
在实际的开发中,假设有一天你真正的遇到了:StackOverflowError 你怎么解决这个问题,可以谈一下你的思路吗?
我来谈一下我的个人思路:
第一步:先检查递归的结束条件对不对。如果递归结束条件不对,必须对条件进一步修改,直到正确为止。
第二步:假设递归条件没问题,怎么办?这个时候需要手动的调整JVM的栈内存初始化大小。可以将栈内存的空间调大点。(可以调整大一些。)
第三步:调整了大小,如果运行时还是出现这个错误,没办法,只能继续扩大栈的内存大小。
(java -X)这个可以查看调整堆栈大小的参数
Java面向对象 面向对象 面向过程有什么缺点?(耦合度高,扩展力差。)
面向过程最主要是每一步与每一步的因果关系,其中A步骤因果关系到B步骤,A和B联合起来形成一个子模块,子模块和子模块之间又因为因果关系结合在一起,假设其中任何一个因果关系出现问题(错误),此时整个系统的运转都会出现问题。(代码和代码之间的耦合度太高,扩展力太差。)
面向过程有什么优点?(快速开发)
对于小型项目(功能),采用面向过程的方式进行开发,效率较高。不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接开始干活。一上来直接写代码,编写因果关系,从而实现功能。
什么是面向对象的开发方式?
什么是类?
什么是对象?对象是实际存在的个体。(真实存在的个体)
这几个术语你需要自己能够阐述出来:
类:不存在的,人类大脑思考总结一个模板(这个模板当中描述了共同特征。)
对象:实际存在的个体。
实例:对象还有另一个名字叫做实例。
实例化:通过类这个模板创建对象的过程,叫做:实例化。
抽象:多个对象具有共同特征,进行思考总结抽取共同特征的过程。
类 –【实例化】–> 对象(实例)
对象 –【抽象】–> 类
类是一个模板,是描述共同特征的一个模板,那么共同特征包括什么呢?
类 = 属性 + 方法
属性来源于:状态
方法来源于:动作
思考:“java软件工程师”在开发中起到的一个作用是什么?
我们为什么要做软件开发?说的大一些是为了人民服务。解决现实生活当中的问题。软件开发既然是为了解决现实世界当中的问题,那么首先java软件必须能够模拟现实世界。其实软件是一个虚拟的世界。这个虚拟的世界需要和现实世界一一对应,这才叫模拟。
类的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 `public class XueSheng { int xueHao; String xingMing; int nianLing; boolean xingBie; String zhuZhi; }
注意:修饰符列表可以省略。
为什么属性是“以”变量的形式存在的?是因为属性对应的是“数据”,数据在程序中只能放到变量中。结论:属性其实就是变量。
变量根据出现位置进行划分:
关于编译的过程
编译方法:
第一种方式:
javac XueSheng.java
javac XueShengTest.java
第二种方式:
第三种方式:
在语法级别上是怎么完成对象创建的呢?
什么是实例变量?
对象又被称为实例。
实例变量实际上就是:对象级别的变量。
1 2 3 public class 明星类{ double height; }
实例变量在访问的时候,是不是必须先创建对象?是
对象和引用的区别?
成员变量的默认值
类型
默认值
byte
0
short
0
int
0
long
0L
float
0.0F
double
0.0
boolean
flase
char
\u0000
引用数据类型
null
内存图 1、画内存图注意事项:
2、为什么要画内存图(非常重要)?
所有的实例变量(属性)都是通过“引用.”来访问的。
引用和对象怎么区分?
“引用”是是存储对象内存地址的一个变量。
“对象”是堆里new出来的。
通俗一点:
只要这个变量中保存的是一个对象的内存地址,那么这个变量就叫做“引用”。
思考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Test { public static void main (String[] args) { Address a = new Address (); a.city = "北京" ; a.street = "大兴区" ; a.zipcode = "121221" ; User u = new User (); System.out.println(u.id); System.out.println(u.username); System.out.println(u.addr); u.id = 11111 ; u.username = "zhangsan" ; u.addr = a; } } public class User { int id; String username; Address addr; } public class Address { String city; String street; String zipcode; }
空指针异常 空指针异常。(NullPointerException)
关于垃圾回收器:GC
在java语言中,垃圾回收器主要针对的是堆内存。当一个java对象没有任何引用指向该对象的时候,GC会考虑将该垃圾数据释放回收掉。
出现空指针异常的前提条件是?”空引用”访问实例【对象相关】相关的数据时,都会出现空指针异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class NullPointerTest { public static void main (String[] args) { Customer c = new Customer (); System.out.println(c.id); c.id = 9521 ; System.out.println("客户的id是=" + c.id); System.out.println(c.id); } } class Customer { int id; }
方法在调用的时候参数是如何传递的?
实际上,在java语言中,方法调用时参数传递,和类型无关,都是将变量中保存的那个“值”传过去,这个“值”可能是一个数字100,也可能是一个java对象的内存地址:0x1234记住这句话:不管是哪一种数据类型的传递,都是将“变量中保存的那个值复制一份传递过去。”
java中关于方法调用时参数传递实际上只有一个规则:
不管你是基本数据类型,还是引用数据类型,实际上在传递的时候都是将变量中保存的那个“值”复制一份,传过去。
1 2 3 4 5 6 7 int x = 1 ;int y = x; Person p1 = 0x1234 ;Person p2 = p1; 把p1中保存的0x1234 复制一份传给p2
你和你媳妇,都有你家大门上的钥匙,钥匙是两把。 但是都可以打开你家的大门。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test2 { public static void main (String[] args) { Person p = new Person (); p.age = 10 ; add(p); System.out.println("main--->" + p.age); } public static void add (Person p) { p.age++; System.out.println("add--->" + p.age); } } class Person { int age; }
构造方法 当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法。这个无参数的构造方法叫做缺省构造器。
当一个类中手动的提供了构造方法,那么系统将不再默认提供无参数构造方法。
建议将无参数构造方法手动的写出来,这样一定不会出问题。
无参数构造方法和有参数的构造方法都可以调用。
1 2 3 Student x = new Student ();Student y = new Student (123 );
构造方法支持方法重载吗?
构造方法是支持方法重载的。
在一个类当中构造方法可以有多个。
并且所有的构造方法名字都是一样的。
特点:在同一个类中,方法名相同,参数列表不同。
对于实例变量来说,只要你在构造方法中没有手动给它赋值,统一都会默认赋值。默认赋系统值。
构造方法需要掌握的知识点:
构造方法有什么作用?
构造方法怎么定义,语法是什么?
构造方法怎么调用,使用哪个运算符?
什么是缺省构造器?
怎么防止缺省构造器丢失?
实例变量在类加载是初始化吗?实例变量在什么时候初始化?
构造方法
1、什么是构造方法,有什么用?
构造方法是用来创建对象,并且同时给对象的属性赋值。(注意:实例变量没有手动赋值的时候,系统会赋默认值。)
2、当一个类没有提供任何构造方法,系统会默认提供一个无参数的构造方法。(而这个构造方法被称为缺省构造器。)
3、调用构造方法怎么调用呢?使用哪个运算符呢?使用new运算符来调用构造方法。new 构造方法名(实际参数列表);
4、构造方法的语法结构是?
1 2 3 4 [修饰符列表] 构造方法名(形式参数列表){ 构造方法体; 通常在构造方法体当中给属性赋值,完成属性的初始化。 }
注意:
修饰符列表目前统一写:public。千万不要写public static。
构造方法名和类名必须一致。
构造方法不需要指定返回值类型,也不能写void,写上void表示普通方法,就不是构造方法了。
普通方法的语法结构是?
1 2 3 [修饰符列表] 返回值类型 方法名(形式参数列表){ 方法体; }
1、构造方法对应的英语单词:Constructor【构造器】
2、构造方法作用:
创建对象,并且创建对象的过程中给属性赋值(初始化。)
以后开发的时候最好自己写出来一个默认无参构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ConstructorTest01 { public static void main (String[] args) { new Student (); ConstructorTest01.doSome(); doSome(); Student s1 = new Student (); System.out.println(s1); Student s3 = new Student (100 ); System.out.println(s3); } public static void doSome () { System.out.println("do some!!!!" ); } }
封装、this和static 封装 有了封装,才有继承,有了继承,才能说多态。
封装的作用有两个:
第一个作用:保证内部结构的安全。
第二个作用:屏蔽复杂,暴露简单。
在代码级别上,封装有什么用?
怎么进行封装
第一步:属性私有化
第二步:1个属性对外提供两个set和get方法。外部程序只能通过set方法修改,只能通过get方法读取,可以在set方法中设立关卡来保证数据的安全性。
在强调一下:
封装过程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Person { private int age; public int getAge () { return age; } public void setAge (int nianLing) { if (nianLing < 0 || nianLing > 150 ){ System.out.println("对不起,年龄值不合法,请重新赋值!" ); return ; } age = nianLing; } }
static方法调用方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class MethodTest { public static void main (String[] args) { MethodTest.doSome(); doSome(); MethodTest mt = new MethodTest (); mt.doOther(); } public static void doSome () { System.out.println("do some!" ); } public void doOther () { System.out.println("do other...." ); } }
再探空指针异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class NullPointerTest { public static void main (String[] args) { User u = new User (); System.out.println(u.id); u.doSome(); u = null ; } } class User { int id; public void doSome () { System.out.println("do some!" ); } public void exam () { } }
static static修饰的统一都是静态的,都是类相关的,不需要new对象。直接采用“类名.”访问。
当一个属性是类级别的属性,所有对象的这个属性的值是一样的,建议定义为静态变量。
实例和静态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 class VarTest { int i; public void m2 () { int x = 200 ; } static int k; public static void m1 () { int m = 100 ; } } class Chinese { String idCard; String name; static String country = "中国" ; public Chinese () { } public Chinese (String s1,String s2) { idCard = s1; name = s2; } }
静态代码块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public class StaticTest06 { static { System.out.println("A" ); } static { System.out.println("B" ); } public static void main (String[] args) { System.out.println("Hello World!" ); } static { System.out.println("C" ); } } public class StaticTest07 { static int i = 100 ; static { System.out.println("i = " + i); } int k = 111 ; static { } static String name = "zhangsan" ; public static void main (String[] args) { System.out.println("main begin" ); System.out.println("main over" ); } }
实例语句块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class InstanceCode { public static void main (String[] args) { System.out.println("main begin" ); new InstanceCode (); new InstanceCode (); new InstanceCode ("abc" ); new InstanceCode ("xyz" ); } { System.out.println("实例语句块执行!" ); } public InstanceCode () { System.out.println("无参数构造方法" ); } public InstanceCode (String name) { System.out.println("有参数的构造方法" ); } }
代码执行顺序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class CodeOrder { static { System.out.println("A" ); } public static void main (String[] args) { System.out.println("Y" ); new CodeOrder (); System.out.println("Z" ); } public CodeOrder () { System.out.println("B" ); } { System.out.println("C" ); } static { System.out.println("X" ); } }
this
this是一个关键字,是一个引用,保存内存地址指向自身。
this可以使用在实例方法中,也可以使用在构造方法中。
this出现在实例方法中其实代表的是当前对象。
this不能使用在静态方法中。
this. 大部分情况下可以省略,但是用来区分局部变量和实例变量的时候不能省略。
this() 这种语法只能出现在构造方法第一行,表示当前构造方法调用本类其他的构造方法,目的是代码复用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 class Customer { String name; public Customer () { } public Customer (String s) { this .name = s; } public void shopping () { System.out.println(name + "正在购物!" ); } public static void doSome () { } } class Student { String name = "zhangsan" ; public static void m1 () { Student s = new Student (); System.out.println(s.name); } public static void setName (String s) { name = s; } public String getName () { return name; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class ThisTest02 { int i = 100 ; static int k = 111 ; public static void main (String[] args) { ThisTest02 tt = new ThisTest02 (); System.out.println(tt.i); System.out.println(ThisTest02.k); System.out.println(k); } }
this什么时候可以省略?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class ThisTest03 { public static void main (String[] args) { Student s = new Student (); s.setNo(111 ); s.setName("张三" ); System.out.println("学号:" + s.getNo()); System.out.println("姓名:" + s.getName()); } } class Student { private int no; private String name; public void setNo (int no) { this .no = no; } public String getName () { return name; } }
this()的使用时机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class ThisTest04 { public static void main (String[] args) { Date d1 = new Date (); d1.detail(); Date d2 = new Date (2008 , 8 , 8 ); d2.detail(); } } class Date { private int year; private int month; private int day; public Date () { this (1970 , 1 , 1 ); } public Date (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } }
this 复习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Review { static { System.out.println("Review类加载时执行!" ); } public static void main (String[] args) { int i = 100 ; Student s1 = new Student (); s1.study(); Student s2 = new Student (); } }
继承 继承的作用: 基本作用:子类继承父类,代码可以得到复用。(这个不是重要的作用,是基本作用。) 主要(重要)作用:因为有了继承关系,才有了后期的方法覆盖和多态机制。
举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Account { private String actno; private double balance; public Account () { } public Account (String actno, double balance) { this .actno = actno; this .balance = balance; } public void setActno (String actno) { this .actno = actno; } public String getActno () { return actno; } public void setBalance (double balance) { this .balance = balance; } public double getBalance () { return balance; } } class CreditAccount extends Account { private double credit; public CreditAccount () { } public void doSome () { System.out.println(getActno()); } public void setCredit (double credit) { this .credit = credit; } public double getCredit () { return credit; } }
相关特性
①B类继承A类,则称A类为超类(superclass)、父类、基类,B类则称为子类(subclass)、派生类、扩展类。 class A{} class B extends A{} 我们平时聊天说的比较多的是:父类和子类。superclass 父类, subclass 子类
java 中的继承只支持单继承,不支持多继承 ,C++中支持多继承,这也是 java 体现简单性的一点,换句话说,java 中不允许这样写代码:class B extends A,C{ } 这是错误的。
虽然 java 中不支持多继承,但有的时候会产生间接继承的效果,例如:class C extends B,class B extends A,也就是说,C 直接继承 B,其实 C 还间接继承 A。
java 中规定,子类继承父类,除构造方法不能继承之外,剩下都可以继承。但是私有的属性无法在子类中直接访问。(父类中private修饰的不能在子类中直接访问。可以通过间接的手段来访问。)
java 中的类没有显示的继承任何类,则默认继承 Object类 ,Object类是java 语言提供的根类(老祖宗类),也就是说,一个对象与生俱来就有Object类型中所有的特征。
继承也存在一些缺点,例如:CreditAccount 类继承 Account 类会导致它们之间的耦合度非常高,Account 类发生改变之后会马上影响到 CeditAccount 类
子类继承父类之后,能使用子类对象调用父类方法吗?可以,因为子类继承了父类之后,这个方法就属于子类了。当然可以使用子类对象来调用。
在实际开发中,满足什么条件的时候,我可以使用继承呢? 凡是采用“is a”能描述的,都可以继承。 例如: Cat is a Animal:猫是一个动物 Dog is a Animal:狗是一个动物 CreditAccount is a Account:信用卡账户是一个银行账户 ….
假设以后的开发中有一个A类,有一个B类,A类和B类确实也有重复的代码, 那么他们两个之间就可以继承吗?不一定,还是要看一看它们之间是否能够 使用is a来描述。
我们研究了一下Object类当中有很多方法,大部分看不懂,其中有一个叫做toString()的,我们进行了测试,发现:System.out.println(引用); 当直接输出一个“引用”的时候,println()方法会先自动调用“引用.toString()”,然后输出toString()方法的执行结果。
方法覆盖和多态 方法覆盖 何时使用
父类中的方法无法满足子类的业务需求,子类有必要对继承过来的方法进行覆盖。
什么条件满足的时候构成方法覆盖?
第一:有继承关系的两个类
第二:具有相同方法名、返回值类型、形式参数列表
第三:访问权限不能更低。
第四:抛出异常不能更多。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 public class OverrideTest02 { public static void main (String[] args) { Bird b = new Bird (); b.move(); b.sing(1000 ); Cat c = new Cat (); c.move(); } } class Animal { public void move () { System.out.println("动物在移动!" ); } public void sing (int i) { System.out.println("Animal sing...." ); } } class Bird extends Animal { public void move () { System.out.println("鸟儿在飞翔!!!" ); } public void sing () { System.out.println("Bird sing....." ); } }
关于Object类中toString()方法的覆盖 toString()方法存在的作用就是:将java对象转换成字符串形式。大多数的java类toString()方法都是需要覆盖的。因为Object类中提供的toString()方法输出的是一个java对象的内存地址。
至于toString()方法具体怎么进行覆盖?格式可以自己定义,或者听需求的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 public class OverrideTest04 { public static void main (String[] args) { MyDate t1 = new MyDate (); System.out.println(t1); Student s = new Student (1111 , "zhangsan" ); System.out.println(s.toString()); System.out.println(s); } } class MyDate { private int year; private int month; private int day; public MyDate () { this (1970 ,1 ,1 ); } ... ... public String toString () { return year + "年" + month + "月" + day + "日" ; } } class Student { int no; String name; public Student (int no, String name) { this .no = no; this .name = name; } public String toString () { return "学号:" + no + ",姓名:" + name; } }
方法重载和方法覆盖的区别 方法重载发生在同一个类当中。 方法覆盖是发生在具有继承关系的父子类之间。 方法重载是一个类中,方法名相同,参数列表不同。 方法覆盖是具有继承关系的父子类,并且重写之后的方法必须和之前的方法一致:方法名一致、参数列表一致、返回值类型一致。
多态 多态的基础语法 向上转型和向下转型的概念
向上转型:子—>父 (upcasting) 又被称为自动类型转换:Animal a = new Cat();
向下转型:父—>子 (downcasting) 又被称为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象 是否为某一种类型。
-> 养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。
不管是向上转型还是向下转型,首先他们之间必须有继承关系,这样编译器就不会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public class Test01 { public static void main (String[] args) { Animal a1 = new Animal (); a1.move(); Cat c1 = new Cat (); c1.move(); Bird b1 = new Bird (); b1.move(); Animal a2 = new Cat (); Animal a3 = new Bird (); a2.move(); a3.move(); Animal a5 = new Cat (); Cat x = (Cat)a5; x.catchMouse(); Animal a6 = new Bird (); System.out.println(a6 instanceof Cat); if (a6 instanceof Cat){ Cat y = (Cat)a6; y.catchMouse(); } } }
多种形态,多种状态,编译和运行有两个不同的状态。 编译期叫做静态绑定。 运行期叫做动态绑定。 Animal a = new Cat(); // 编译的时候编译器发现a的类型是Animal,所以编译器会去Animal类中找move()方法 // 找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关 // 真正执行的时候会自动调用“堆内存中真实对象”的相关方法。 a.move();
多态的典型代码:父类型的引用指向子类型的对象。(java中允许这样写代码!!!)
什么时候必须进行向下转型?调用子类对象上特有的方法时。
为什么要用instanceof 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class AnimalTest { public void test (Animal a) { if (a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); }else if (a instanceof Bird){ Bird b = (Bird)a; b.sing(); } } }
多态在开发中的作用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
非常重要:五颗星。
多态在开发中的作用是:降低程序的耦合度,提高程序的扩展力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Master {public void feed (Dog d) {}public void feed (Cat c) {}} public class Master {public void feed (Pet pet) {pet.eat(); } }
有了封装,有了这种整体的概念之后。 对象和对象之间产生了继承。 有了继承之后,才有了方法的覆盖和多态。
这里提到了一个软件开发原则: 七大原则最基本的原则:OCP(对扩展开放,对修改关闭) 目的是:降低程序耦合度,提高程序扩展力。 面向抽象编程,不建议面向具体编程。
解释之前遗留的问题 私有方法无法覆盖。
方法覆盖只是针对于“实例方法”,“静态方法覆盖”没有意义。(这是因为方法覆盖通常和多态联合起来)
总结两句话:私有不能覆盖。静态不谈覆盖。
在方法覆盖中,关于方法的返回值类型。 什么条件满足之后,会构成方法的覆盖呢?
发生具有继承关系的两个类之间。
父类中的方法和子类重写之后的方法:具有相同的方法名、相同的形式参数列表、相同的返回值类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class OverrideTest05 { public static void main (String[] args) { Animal a = new Cat (); a.doSome(); Animal.doSome(); Cat.doSome(); } }
“相同的返回值类型”可以修改一下吗?
对于返回值类型是基本数据类型来说,必须一致。
对于返回值类型是引用数据类型来说,重写之后返回值类型可以变的更小(但意义不大,实际开发中没人这样写。)。
super 与this相比
super能出现在实例方法和构造方法中。
super的语法是:“super.”、“super()”
super不能使用在静态方法中。
super. 大部分情况下是可以省略的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 public class SuperTest01 { public static void main (String[] args) { new B (); } } class A { public A () { System.out.println("A类的无参数构造方法!" ); } public A (int i) { System.out.println("A类的有参数构造方法(int)" ); } } class B extends A { public B () { this ("zhangsan" ); System.out.println("B类的无参数构造方法!" ); } public B (String name) { super (); System.out.println("B类的有参数构造方法(String)" ); } }
super的使用: super.属性名 【访问父类的属性】 super.方法名(实参) 【访问父类的方法】 super(实参) 【调用父类的构造方法】
什么时候可以省略 super.什么时候不能省略呢?
父类和子类中有同名属性,或者说有同样的方法,想在子类中访问父类的,super. 不能省略。
super() 只能出现在构造方法第一行,通过当前的构造方法去调用“父类”中 的构造方法,目的是:创建子类对象的时候,先初始化父类型特征。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public void shopping () { System.out.println(this .name + "正在购物!" ); System.out.println(super .name + "正在购物!" ); System.out.println(name + "正在购物!" ); } } public class SuperTest06 { public void doSome () { System.out.println(this ); } public static void main (String[] args) { SuperTest06 st = new SuperTest06 (); st.doSome(); } }
final关键字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 class A {} final class B extends A {} class C { public final void f () {} } class D extends C { } class E { public static void main (String[] args) { final int i = 10 ; } }
final修饰的类无法继承。
final修饰的方法无法覆盖。
final修饰的变量只能赋一次值。
final修饰的引用一旦指向某个对象,则不能再重新指向其它对象,但该引用
指向的对象内部的数据是可以修改的。
final修饰的实例变量必须手动初始化,不能采用系统默认值。
final修饰的实例变量一般和static联合使用,称为常量。
public static final double PI = 3.1415926;
抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 public class AbstractTest01 { public static void main (String[] args) { } } abstract class Account { public void doOther () {} public abstract void withdraw () ; } public class AbstractTest02 { public static void main (String[] args) { Animal a = new Bird (); a.move(); Animal x = new Cat (); x.move(); } abstract class Animal { public abstract void move () ; } class Bird extends Animal { public void move () { System.out.println("鸟儿在飞翔!" ); } } class Cat extends Animal { public void move () { System.out.println("猫在走猫步!" ); } }
抽象类怎么定义?在class前添加abstract关键字就行了。
抽象类是无法实例化的,无法创建对象的,所以抽象类是用来被子类继承的。
final和abstract不能联合使用,这两个关键字是对立的。
抽象类的子类可以是抽象类。也可以是非抽象类。
抽象类虽然无法实例化,但是抽象类有构造方法,这个构造方法是供子类使用的。
抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中。
抽象方法怎么定义?public abstract void doSome();
一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖/重写/实现。
面试题(判断题):java语言中凡是没有方法体的方法都是抽象方法。错误的。 Object类中就有很多方法都没有方法体,都是以“;”结尾的,但他们都不是抽象方法,例如:public native int hashCode();这个方法底层调用了C++写的动态链接库程序。前面修饰符列表中没有:abstract。有一个native。表示调用JVM本地程序。
接口 基础语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class Test01 { public static void main (String[] args) { System.out.println(MyMath.PI); } } interface A {} interface B extends A {} interface C extends A , B{} interface MyMath { double PI = 3.1415926 ; int sum (int a, int b) ; int sub (int a, int b) ; }
接口是一种“引用数据类型”。
接口是完全抽象的。
接口怎么定义:[修饰符列表] interface 接口名{}
接口支持多继承。
接口中只有常量+抽象方法。
接口中所有的元素都是public修饰的
接口中抽象方法的public abstract可以省略。
接口中常量的public static final可以省略。
接口中方法不能有方法体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class Test02 { public static void main (String[] args) { MyMath mm = new MyMathImpl (); int result1 = mm.sum(10 , 20 ); System.out.println(result1); } } interface MyMath { double PI = 3.1415926 ; int sum (int a, int b) ; int sub (int a, int b) ; } class MyMathImpl implements MyMath { public int sum (int a, int b) { return a + b; } public int sub (int a, int b) { return a - b; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class Test03 { public static void main (String[] args) { A a = new D (); B b = new D (); C c = new D (); B b2 = (B)a; b2.m2(); D d = (D)a; d.m2(); M m = new E (); if (m instanceof K){ K k = (K)m; } } } interface K {}interface M {}class E implements M {}interface X {}interface Y {}interface Z extends X ,Y{ interface A {void m1 () ;}interface B {void m2 () ;}interface C {void m3 () ;}class D implements A ,B,C{ public void m1 () {} public void m2 () {} public void m3 () {} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Test04 { public static void main (String[] args) { Flyable f = new Cat (); f.fly(); Flyable f2 = new Pig (); f2.fly(); Flyable f3 = new Fish (); f3.fly(); } } class Animal {}interface Flyablevoid fly();}class Cat extends Animal implements Flyable { public void fly () { System.out.println("飞猫起飞,翱翔太空的一只猫,很神奇,我想做一只猫!!" ); } } class Snake extends Animal {} class Pig extends Animal implements Flyable { public void fly () { System.out.println("我是一只会飞的猪!!!" ); } } class Fish implements Flyable { public void fly () { System.out.println("我是六眼飞鱼(流言蜚语)!!!" ); } }
一个非抽象的类,实现接口的时候,必须将接口中所有方法加以实现。
一个类可以实现多个接口。
extends和implements可以共存,extends在前,implements在后。
使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。
在开发中的作用(解耦合) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 public class Customer { private FoodMenu foodMenu; public Customer () { } public Customer (FoodMenu foodMenu) { this .foodMenu = foodMenu; } public void setFoodMenu (FoodMenu foodMenu) { this .foodMenu = foodMenu; } public FoodMenu getFoodMenu () { return foodMenu; } public void order () { foodMenu.shiZiChaoJiDan(); foodMenu.yuXiangRouSi(); } } public class AmericCooker implements FoodMenu { public void shiZiChaoJiDan () { System.out.println("西餐师傅做的西红柿炒鸡蛋!" ); } public void yuXiangRouSi () { System.out.println("西餐师傅做的鱼香肉丝!" ); } } public class ChinaCooker implements FoodMenu { public void shiZiChaoJiDan () { System.out.println("中餐师傅做的西红柿炒鸡蛋,东北口味!" ); } public void yuXiangRouSi () { System.out.println("中餐师傅做的鱼香肉丝,东北口味!" ); } } public interface FoodMenu { void shiZiChaoJiDan () ; void yuXiangRouSi () ; } public class Test { public static void main (String[] args) { FoodMenu cooker1 = new AmericCooker (); Customer customer = new Customer (cooker1); customer.order(); }
注意:接口在开发中的作用,类似于多态在开发中的作用。
多态:面向抽象编程,不要面向具体编程。降低程序的耦合度。提高程序的扩展力。
接口在开发中的作用? 接口是不是完全的?是。 而我们以后正好要求,面向抽象编程。面向抽象编程这句话以后可以修改为:面向接口编程。有了接口就有了可插拔。可插拔表示扩展力很强。不是焊接死的。 主板和内存条之间有插槽,这个插槽就是接口,内存条坏了,可以重新买一个换下来。这叫做高扩展性。(低耦合度。)
接口有什么用?扩展性好。可插拔。 接口是一个抽象的概念。
总结一句话:三个字“解耦合” 面向接口编程,可以降低程序的耦合度,提高程序的扩展力。符合OCP开发原则。接口的使用离不开多态机制。(接口+多态才可以达到降低耦合度。)
接口可以解耦合,解开的是谁和谁的耦合 ? 任何一个接口都有调用者和实现者。接口可以将调用者和实现者解耦合。调用者面向接口调用。实现者面向接口编写实现。
以后进行大项目的开发,一般都是将项目分离成一个模块一个模块的, 模块和模块之间采用接口衔接。降低耦合度。
类型之间的关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
抽象类和接口的区别 在这里我们只说一下抽象类和接口在语法上的区别。 至于以后抽象类和接口应该怎么进行选择,通过后面的项目去体会/学习。
抽象类是半抽象的。 接口是完全抽象的。
抽象类中有构造方法。 接口中没有构造方法。
接口和接口之间支持多继承。 类和类之间只能单继承。
一个类可以同时实现多个接口。 一个抽象类只能继承一个类(单继承)。
接口中只允许出现常量和抽象方法。
这里先透露一个信息: 以后接口使用的比抽象类多。一般抽象类使用的还是少。 接口一般都是对“行为”的抽象。
package和import 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.bjpowernode.javase.chapter17;import com.bjpowernode.javase.chapter17.*;public class HelloWorld { public static void main (String[] args) { System.out.println("Hello World!" ); } } import java.util.*;public class Test03 { public static void main (String[] args) { Scanner s = new Scanner (System.in); String str = s.next(); System.out.println("您输入的字符串是--->" + str); } }
package出现在java源文件第一行。
带有包名怎么编译?javac -d . xxx.java
怎么运行?java 完整类名
补充:以后说类名的时候,如果带着包名描述,表示完整类名。 如果没有带包,描述的话,表示简类名。 java.util.Scanner 完整类名。 Scanner 简类名
import什么时候不需要? java.lang不需要。 同包下不需要。 其它一律都需要。
怎么用? import 完整类名; import 包名.*;
import java.util.Scanner; // 完整类名。
// 同学的疑问:这样是不是效率比较低。 // 这个效率不低,因为编译器在编译的时候,会自动把变成具体的类名。 import java.util. ;
// 想省懒劲你不能太省了。 import java.; 这是不允许的,因为在java语言中规定,这里的 只代表某些类的名字。
objective类 目前为止我们只需要知道这几个方法即可:
protected Object clone() // 负责对象克隆的。
int hashCode()// 获取对象哈希值的一个方法。
boolean equals(Object obj) // 判断两个对象是否相等
String toString() // 将对象转换成字符串形式
protected void finalize() // 垃圾回收器负责调用的方法
toString() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Test01 { public static void main (String[] args) { MyTime t1 = new MyTime (1970 , 1 , 1 ); String s1 = t1.toString(); System.out.println(s1); System.out.println(t1); } } class MyTime { int year; int month; int day; public MyTime () { } public MyTime (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } public String toString () { return this .year + "/" + this .month + "/" + this .day; } }
以后所有类的toString()方法是需要重写的。
重写规则,越简单越明了就好。
System.out.println(引用); 这里会自动调用“引用”的toString()方法。
ring类是SUN写的,toString方法已经重写了。
equals() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 public class Test02 { public static void main (String[] args) { int a = 100 ;int b = 100 ; System.out.println(a == b); MyTime t1 = new MyTime (2008 , 8 , 8 ); MyTime t2 = new MyTime (2008 , 8 , 8 ); System.out.println(t1 == t2); boolean flag = t1.equals(t2); System.out.println(flag); MyTime t3 = new MyTime (2008 , 8 , 9 ); System.out.println(t1.equals(t3)); MyTime t4 = null ; System.out.println(t1.equals(t4)); } } class MyTime { int year;int month;int day; public MyTime () {} public MyTime (int year, int month, int day) { this .year = year; this .month = month; this .day = day; } public boolean equals (Object obj) { int year1 = this .year; int month1 = this .month; int day1 = this .day; if (obj instanceof MyTime){ MyTime t = (MyTime)obj; int year2 = t.year; int month2 = t.month; int day2 = t.day; if (year1 == year2 && month1 == month2 && day1 == day2){ return true ; } } return false ; } public boolean equals (Object obj) { if (obj == null ){ return false ; } if (!(obj instanceof MyTime)){ return false ; } if (this == obj){ return true ; } MyTime t = (MyTime)obj; return this .year == t.year && this .month == t.month && this .day == t.day ; } public boolean equals (Object obj) { if (obj == null || !(obj instanceof MyTime)){ return false ; } if (this == obj){ return true ; } MyTime t = (MyTime)obj; return this .year == t.year && this .month == t.month && this .day == t.day ; } } public class Test03 { public static void main (String[] args) { String s1 = "hello" ; String s2 = "abc" ; String s3 = new String ("Test1" ); String s4 = new String ("Test1" ); System.out.println(s3 == s4); System.out.println(s3.equals(s4)); String x = new String ("动力节点" ); System.out.println(x.toString()); System.out.println(x); } } class Student { int no; String school; public Student () {} public Student (int no,String school) { this .no = no; this .school = school; } public String toString () { return "学号" + no + ",所在学校名称" + school; } public boolean equals (Object obj) { if (obj == null || !(obj instanceof Student)) return false ; if (this == obj) return true ; Student s = (Student)obj; return this .no == s.no && this .school.equals(s.school); } } public class Test05 { public static void main (String[] args) { Object o1 = new String ("hello world!" ); Object o2 = new User (); Object o3 = new Address (); User u1 = new User ("zhangsan" , new Address ("北京" ,"大兴区" ,"11111" )); User u2 = new User ("zhangsan" , new Address ("北京" ,"大兴区" ,"11111" )); System.out.println(u1.equals(u2)); User u3 = new User ("zhangsan" , new Address ("北京" ,"朝阳区" ,"11112" )); System.out.println(u1.equals(u3)); } } class User { String name; Address addr; public User () { } public User (String name, Address addr) { this .name = name; this .addr = addr; } public boolean equals (Object obj) { if (obj == null || !(obj instanceof User)) return false ; if (this == obj) return true ; User u = (User)obj; if (this .name.equals(u.name) && this .addr.equals(u.addr)){ return true ; } return false ; } } class Address { String city; String street; String zipcode; public Address () { } public Address (String city,String street,String zipcode) { this .city = city; this .street = street; this .zipcode = zipcode; } public boolean equals (Object obj) { if (obj == null || !(obj instanceof Address)) return false ; if (this == obj) return true ; Address a = (Address)obj; if (this .city.equals(a.city) && this .street.equals(a.street) && this .zipcode.equals(a.zipcode)){ return true ; } return false ; } }
以后所有类的equals方法也需要重写,因为Object中的equals方法比较的是两个对象的内存地址,我们应该比较内容,所以需要重写。
重写规则:自己定,主要看是什么和什么相等时表示两个对象相等。
基本数据类型比较实用:==
对象和对象比较:调用equals方法
String类是SUN编写的,所以String类的equals方法重写了。
以后判断两个字符串是否相等,最好不要使用==,要调用字符串对象的equals方法。
注意:重写equals方法的时候要彻底。
finalize()【非重点】【jdk9之后就没了】 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class Test06 { public static void main (String[] args) {
这个方法是protected修饰的,在Object类中这个方法的源代码是? protected void finalize() throws Throwable { }
hashCode 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Test07 { public static void main (String[] args) { Object o = new Object (); int hashCodeValue = o.hashCode(); System.out.println(hashCodeValue); MyClass mc = new MyClass (); int hashCodeValue2 = mc.hashCode(); System.out.println(hashCodeValue2); MyClass mc2 = new MyClass (); System.out.println(mc2.hashCode()); } } class MyClass { }
匿名内部类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 class Test01 { static String country; static class Inner1 { } int age; class Inner2 { } public void doSome () { int i = 100 ; class Inner3 { } } public void doOther () { } public static void main (String[] args) { MyMath mm = new MyMath (); mm.mySum(new Compute (){ public int sum (int a, int b) { return a + b; } }, 200 , 300 ); } } interface Compute { int sum (int a, int b) ; } class MyMath { public void mySum (Compute c, int x, int y) { int retValue = c.sum(x, y); System.out.println(x + "+" + y + "=" + retValue); } }
访问控制权限 访问控制权限都有哪些?4个。
private私有 只能在本类中访问
public 公开 任何地方都能访问
protected受保护 只能在本类、同包、子类中访问。
默认 只能在本类,以及同包下访问。
访问控制修饰符
本类
同包
子类
任意位置
public
可以
可以
可以
可以
protected
可以
可以
可以
不行
private
可以
不行
不行
不行
默认
可以
可以
不行
不行
这个不要死记硬背,自己下去之后编写代码自己测试。
范围从大到小排序:public > protected > 默认 > private
访问控制权限修饰符可以修饰什么?
属性(4个都能用)
方法(4个都能用)
类(public和默认能用,其它不行。)
接口(public和默认能用,其它不行。)
数组和常用类 数组 Java语言中的数组是一种引用数据类型。不属于基本数据类型。数组的父类是Object
数组实际上是一个容器,可以同时容纳多个元素。(数组是一个数据的集合。)数组:字面意思是“一组数据”
数组当中可以存储“基本数据类型”的数据,也可以存储“引用数据类型”的数据
数组是存储在堆当中的
数组当中如果存储的是“java对象”的话,实际上存储的是对象的“引用(内存地址)”,数组中不能直接存储java对象。
数组长度不可变
所有的数组对象都有length属性 (java自带的),用来获取数组中元素的个数。
java中的数组要求数组中元素的类型统一。比如int类型数组只能存储int类型,Person类型数组只能存储Person类型。
数组在内存方面存储的时候,数组中的元素内存地址(存储的每一个元素都是有规则的挨着排列的)是连续的。内存地址连续。 这是数组存储元素的特点(特色)。数组实际上是一种简单的数据结构。
所有的数组都是拿“第一个小方框的内存地址”作为整个数组对象的内存地址。 (数组中首元素的内存地址作为整个数组对象的内存地址。)
数组中每一个元素都是有下标的,下标从0开始,以1递增。最后一个元素的下标是:length - 1
数组这种数据结构的优点和缺点是什么?
优点 :查询/查找/检索某个下标上的元素时效率极高。可以说是查询效率最高的一个数据结构。
缺点 : 1.由于为了保证数组中每个元素的内存地址连续,所以在数组上随机删除或者增加元素的时候,效率较低,因为随机增删元素会涉及到后面元素统一向前或者向后位移的操作。(注意:对于数组中最后一个元素的增删,是没有效率影响的。) 2.数组不能存储大数据量。为什么? 因为很难在内存空间上找到一块特别大的连续的内存空间。
为什么检索效率高?
1.每一个元素的内存地址在空间存储上是连续的。 2.每一个元素类型相同,所以占用空间大小一样。 3.知道第一个元素内存地址,知道每一个元素占用空间的大小,又知道下标,所以通过一个数学表达式就可以计算出某个下标上元素的内存地址。直接通过内存地址定位 元素,所以数组的检索效率是最高的。
1 2 3 4 5 6 7 8 9 public class ArrayTest01 { public static void main (String[] args) { int [] a = {1 , 100 , 10 , 20 , 55 , 689 }; } }
什么时候采用静态初始化方式,什么时候使用动态初始化方式呢?
当你创建数组的时候,确定数组中存储哪些具体的元素时,采用静态初始化方式。
当你创建数组的时候,不确定将来数组中存储哪些数据,你可以采用动态初始化的方式,预先分配内存空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ArrayTest02 { public static void main (String[] args) { int [] a = new int [4 ]; Object[] objs = new Object [3 ]; String[] strs2 = {"abc" , "def" , "xyz" }; for (int i = 0 ; i < strs2.length; i++) { System.out.println(strs2[i]); } Object[] objects = {new Object (), new Object (), new Object ()}; } } } public class ArrayTest04 { public static void main (String[] args) { int [] a = {1 ,2 ,3 }; printArray(a); printArray(new int []{1 ,2 ,3 }); int [] a2 = new int [4 ]; printArray(a2); printArray(new int [3 ]); } }
main方法的String[] 参数 1 2 3 4 5 6 7 8 9 10 11 12 13 public class ArrayTest05 { public static void main (String[] args) { System.out.println("JVM给传递过来的String数组参数,它这个数组的长度是?" + args.length); } }
引用数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class ArrayTest07 { public static void main (String[] args) { Cat c = new Cat (); Bird b = new Bird (); Animal[] anis = {c, b}; for (int i = 0 ; i < anis.length; i++){ if (anis[i] instanceof Cat){ Cat cat = (Cat)anis[i]; cat.catchMouse(); }else if (anis[i] instanceof Bird){ Bird bird = (Bird)anis[i]; bird.sing(); } } } }
数组的扩容和拷贝 关于一维数组的扩容。
在java开发中,数组长度一旦确定不可变,那么数组满了怎么办?扩容。
java中对数组的扩容是:先新建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中。
结论:数组扩容效率较低。因为涉及到拷贝的问题。所以在以后的开发中请注意:尽可能少的进行数组的拷贝。
可以在创建数组对象的时候预估计以下多长合适,最好预估准确,这样可以减少数组的扩容次数。提高效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ArrayTest08 { public static void main (String[] args) { int [] src = {1 , 11 , 22 , 3 , 4 }; int [] dest = new int [20 ]; System.arraycopy(src, 0 , dest, 0 , src.length); String[] strs = {"hello" , "world!" , "study" , "java" , "oracle" , "mysql" , "jdbc" }; String[] newStrs = new String [20 ]; System.arraycopy(strs, 0 , newStrs, 0 , strs.length); System.out.println("================================" ); Object[] objs = {new Object (), new Object (), new Object ()}; Object[] newObjs = new Object [5 ]; System.arraycopy(objs, 0 , newObjs, 0 , objs.length); } }
二维数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class ArrayTest11 { public static void main (String[] args) { String[][] array = { {"java" , "oracle" , "c++" , "python" , "c#" }, {"张三" , "李四" , "王五" }, {"lucy" , "jack" , "rose" } }; for (int i = 0 ; i < array.length; i++){ for (int j = 0 ; j < array[i].length; j++){ System.out.print(array[i][j] + " " ); } System.out.println(); } } } public class ArrayTest12 { public static void main (String[] args) { int [][] array = new int [3 ][4 ]; int [][] a = {{1 ,2 ,3 ,4 },{4 ,5 ,6 ,76 },{1 ,23 ,4 }}; printArray(a); printArray(new int [][]{{1 ,2 ,3 ,4 },{4 ,5 ,6 ,76 },{1 ,23 ,4 }}); } public static void printArray (int [][] array) { for (int i = 0 ; i < array.length; i++) { for (int j = 0 ; j < array[i].length; j++) { System.out.print(array[i][j] + " " ); } System.out.println(); } } }
每次都重写toString和equals方法 !!!!
第一:空间存储上,内存地址是连续的。
第二:每个元素占用的空间大小相同。
第三:知道首元素的内存地址。
第四:通过下标可以计算出偏移量。
优点:检索效率高。直接通过内存地址定位,效率非常高。 缺点:随机增删效率较低,数组无法存储大数据量。 注意:数组最后一个元素的增删效率不受影响。
排序和查找 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import java.util.Arrays;public class ArraysTest01 { public static void main (String[] args) { int [] arr = {112 ,3 ,4 ,56 ,67 ,1 }; Arrays.sort(arr); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } } import java.util.Arrays;public class ArraysTest02 { public static void main (String[] args) { int [] arr = {3 ,6 ,4 ,5 ,12 ,1 ,2 ,32 ,5 ,5 }; Arrays.sort(arr); for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } int index = Arrays.binarySearch(arr, 5 ); System.out.println(index == -1 ? "该元素不存在" : "该元素下标是:" + index); } }
常用类 String类 基础认知 java.lang.String
String表示字符串类型,属于引用数据类型,不属于基本数据类型。
在java中随便使用双引号括起来的都是String对象。例:”abc”,”def”,”hello world!”,这是3个String对象。
java中规定,双引号括起来的字符串,是不可变的,也就是说”abc”自出生到最终死亡,不可变,不能变成”abcd”,也不能变成”ab”
在JDK当中双引号括起来的字符串,例如:”abc” “def”都是直接存储在“方法区”的“字符串常量池”当中的。为什么SUN公司把字符串存储在一个“字符串常量池”当中呢。因为字符串在实际的开发中使用太频繁。为了执行效率,所以把字符串放到了方法区的字符串常量池当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class StringTest01 { public static void main (String[] args) { String s1 = "abcdef" ; String s2 = "abcdef" + "xy" ; String s3 = new String ("xy" ); int i = 100 ; String s = "abc" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class StringTest02 { public static void main (String[] args) { String s1 = "hello" ; String s2 = "hello" ; System.out.println(s1 == s2); String x = new String ("xyz" ); String y = new String ("xyz" ); System.out.println(x == y); System.out.println(x.equals(y)); String k = new String ("testString" ); System.out.println("testString" .equals(k)); System.out.println(k.equals("testString" )); } }
分析以下程序,一共创建了几个对象
1 2 3 4 5 6 public class StringTest03 { public static void main (String[] args) { String s1 = new String ("hello" ); String s2 = new String ("hello" ); } }
一共3个对象:
方法区字符串常量池中有1个:”hello”堆内存当中有两个String对象,一共3个。
构造方法 关于String类中的构造方法。
第一个:String s = new String(“”);
第二个:String s = “”; 最常用
第三个:String s = new String(char数组);
第四个:String s = new String(char数组,起始下标,长度);
第五个:String s = new String(byte数组);
第六个:String s = new String(byte数组,起始下标,长度)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class StringTest04 { public static void main (String[] args) { String s1 = "hello world!" ; System.out.println(s1); System.out.println(s1.toString()); byte [] bytes = {97 , 98 , 99 }; String s2 = new String (bytes); System.out.println(s2.toString()); System.out.println(s2); String s3 = new String (bytes, 1 , 2 ); System.out.println(s3); char [] chars = {'我' ,'是' ,'中' ,'国' ,'人' }; String s4 = new String (chars); System.out.println(s4); String s5 = new String (chars, 2 , 3 ); System.out.println(s5); String s6 = new String ("helloworld!" ); System.out.println(s6); } }
常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 public class StringTest05 { public static void main (String[] args) { char c = "中国人" .charAt(1 ); System.out.println(c); int result = "abc" .compareTo("abc" ); System.out.println(result); int result2 = "abcd" .compareTo("abce" ); System.out.println(result2); int result3 = "abce" .compareTo("abcd" ); System.out.println(result3); System.out.println("xyz" .compareTo("yxz" )); System.out.println("HelloWorld.java" .contains(".java" )); System.out.println("test.txt" .endsWith(".java" )); System.out.println("test.txt" .endsWith(".txt" )); System.out.println("abc" .equals("abc" )); System.out.println("ABc" .equalsIgnoreCase("abC" )); byte [] bytes = "abcdef" .getBytes(); for (int i = 0 ; i < bytes.length; i++){ System.out.println(bytes[i]); } System.out.println("oraclejavac++.netc#phppythonjavaoraclec++" .indexOf("java" )); String s = "a" ; System.out.println(s.isEmpty()); System.out.println("abc" .length()); System.out.println("" .length()); System.out.println("oraclejavac++javac#phpjavapython" .lastIndexOf("java" )); String newString = "http://www.baidu.com" .replace("http://" , "https://" ); System.out.println(newString); String newString2 = "name=zhangsan&password=123&age=20" .replace("=" , ":" ); System.out.println(newString2); String[] ymd = "1980-10-11" .split("-" ); System.out.println("http://www.baidu.com" .startsWith("http" )); System.out.println("http://www.baidu.com" .substring(7 )); System.out.println("http://www.baidu.com" .substring(7 , 10 )); char [] chars = "我是中国人" .toCharArray(); for (int i = 0 ; i < chars.length; i++){ System.out.println(chars[i]); } System.out.println("ABCDefKXyz" .toLowerCase()); System.out.println("ABCDefKXyz" .toUpperCase()); System.out.println(" hello world " .trim()); String s1 = String.valueOf(new Customer ()); System.out.println(s1); System.out.println(100 ); System.out.println(3.14 ); System.out.println(true ); Object obj = new Object (); System.out.println(obj); System.out.println(new Customer ()); } } class Customer { @Override public String toString () { return "我是一个VIP客户!!!!" ; } }
如果以后需要进行大量字符串的拼接操作,建议使用JDK中自带的:
java.lang.StringBuffer
java.lang.StringBuilder
如何优化StringBuffer的性能?
在创建StringBuffer的时候尽可能给定一个初始化容量。
最好减少底层数组的扩容次数。预估计一下,给一个大一些初始化容量。
关键点:给一个合适的初始化容量。可以提高程序的执行效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class StringBufferTest02 { public static void main (String[] args) { StringBuffer stringBuffer = new StringBuffer (); stringBuffer.append("a" ); stringBuffer.append(3.14 ); stringBuffer.append(true ); stringBuffer.append(100L ); System.out.println(stringBuffer.toString()); StringBuffer sb = new StringBuffer (100 ); sb.append("hello" ); System.out.println(sb); } }
面试题:String为什么是不可变的?
我看过源代码,String类中有一个byte[]数组,这个byte[]数组采用了final修饰,因为数组一旦创建长度不可变。并且被final修饰的引用一旦指向某个对象之后,不可再指向其它对象,所以String是不可变的!”abc” 无法变成 “abcd”
StringBuilder/StringBuffer为什么是可变的呢?
我看过源代码,StringBuffer/StringBuilder内部实际上是一个byte[]数组, 这个byte[]数组没有被final修饰,StringBuffer/StringBuilder的初始化容量我记得应该是16,当存满之后会进行扩容,底层调用了数组拷贝的方法System.arraycopy()…是这样扩容的。所以StringBuilder/StringBuffer适合于使用字符串的频繁拼接操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.bjpowernode.javase.stringbuffer;public class StringBufferTest04 { public static void main (String[] args) { String s = "abc" ; s = "xyz" ; } }
java.lang.StringBuilder
StringBuffer和StringBuilder的区别?
StringBuffer中的方法都有:synchronized关键字修饰。表示StringBuffer在多线程环境下运行是安全的。
StringBuilder中的方法都没有:synchronized关键字修饰,表示StringBuilder在多线程环境下运行是不安全的。
1 2 3 4 5 6 7 8 9 10 11 12 public class StringBuilderTest01 { public static void main (String[] args) { StringBuilder sb = new StringBuilder (); sb.append(100 ); sb.append(true ); sb.append("hello" ); sb.append("kitty" ); System.out.println(sb); } }
基本数据类型对应的包装类 包装类存在有什么用?方便编程。
八种包装类的类名是什么?
Byte
Short
Integer
Long
Float
Double
Boolean
Character
所有数字的父类Number 照葫芦画瓢:学习Integer,其它的模仿Integer。
这里只写Integer的包装类,其他照葫芦画瓢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class IntegerTest01 { public static void main (String[] args) { } public static void doSome (Object obj) { System.out.println(obj.toString()); } }
基本数据类型
包装类型
byte
java.lang.Byte(父类Number)
short
java.lang.Short(父类Number)
int
java.lang.Integer(父类Number)
long
java.lang.Long(父类Number)
float
java.lang.Float(父类Number)
double
java.lang.Double(父类Number)
boolean
java.lang.Boolean(父类Object)
char
java.lang.Character(父类Object)
以上八种包装类中,重点以java.lang.Integer为代表进行学习,其它的类型照葫芦画瓢就行。
八种包装类中其中6个都是数字对应的包装类,他们的父类都是Number,可以先研究一下Number中公共的方法:Number是一个抽象类,无法实例化对象。 Number类中有这样的方法:
byte byteValue() 以 byte 形式返回指定的数值。
abstract double doubleValue()以 double 形式返回指定的数值。
abstract float floatValue()以 float 形式返回指定的数值。
abstract int intValue()以 int 形式返回指定的数值。
abstract long longValue()以 long 形式返回指定的数值。
short shortValue()以 short 形式返回指定的数值。
这些方法其实所有的数字包装类的子类都有,这些方法是负责拆箱的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 public class IntegerTest02 { public static void main (String[] args) { Integer i = new Integer (123 ); float f = i.floatValue(); System.out.println(f); int retValue = i.intValue(); System.out.println(retValue); } } public class IntegerTest05 { public static void main (String[] args) { Integer x = 900 ; int y = x; Integer z = 1000 ; System.out.println(z + 1 ); Integer a = 1000 ; Integer b = 1000 ; System.out.println(a == b); } } public class IntegerTest06 { public static void main (String[] args) { Integer a = 128 ; Integer b = 128 ; System.out.println(a == b); Integer x = 127 ; Integer y = 127 ; System.out.println(x == y); } }
总结一下以前遇到的经典的异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class IntegerTest07 { public static void main (String[] args) { Integer x = new Integer (1000 ); int y = x.intValue(); Integer a = new Integer ("123" ); int retValue = Integer.parseInt("123" ); System.out.println(retValue + 100 ); double retValue2 = Double.parseDouble("3.14" ); System.out.println(retValue2 + 1 ); float retValue3 = Float.parseFloat("1.0" ); System.out.println(retValue3 + 1 ); String binaryString = Integer.toBinaryString(3 ); System.out.println(binaryString); String hexString = Integer.toHexString(16 ); System.out.println(hexString); hexString = Integer.toHexString(17 ); System.out.println(hexString); String octalString = Integer.toOctalString(8 ); System.out.println(octalString); System.out.println(new Object ()); Integer i1 = Integer.valueOf(100 ); System.out.println(i1); Integer i2 = Integer.valueOf("100" ); System.out.println(i2); } }
Integer String int三种类型互相转换
日期类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 import java.text.SimpleDateFormat;import java.util.Date;public class DateTest01 { public static void main (String[] args) throws Exception { Date nowTime = new Date (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss SSS" ); String nowTimeStr = sdf.format(nowTime); System.out.println(nowTimeStr); String time = "2008-08-08 08:08:08 888" ; SimpleDateFormat sdf2 = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss SSS" ); Date dateTime = sdf2.parse(time); System.out.println(dateTime); } } public class DateTest02 { public static void main (String[] args) { long nowTimeMillis = System.currentTimeMillis(); System.out.println(nowTimeMillis); long begin = System.currentTimeMillis(); print(); long end = System.currentTimeMillis(); System.out.println("耗费时长" +(end - begin)+"毫秒" ); } public static void print () { for (int i = 0 ; i < 1000000000 ; i++){ System.out.println("i = " + i); } } } public class DateTest03 { public static void main (String[] args) { Date time = new Date (1 ); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss SSS" ); String strTime = sdf.format(time); System.out.println(strTime); Date time2 = new Date (System.currentTimeMillis() - 1000 * 60 * 60 * 24 ); String strTime2 = sdf.format(time2); System.out.println(strTime2); } }
获取系统当前时间
Date d = new Date();
日期格式化:Date –> String
yyyy-MM-dd HH:mm:ss SSS
SimpleDateFormat sdf = new SimpleDate(“yyyy-MM-dd HH:mm:ss SSS”);
String s = sdf.format(new Date());
String –> Date
SimpleDateFormat sdf = new SimpleDate(“yyyy-MM-dd HH:mm:ss”);
Date d = sdf.parse(“2008-08-08 08:08:08”);
获取毫秒数
long begin = System.currentTimeMillis();
Date d = new Date(begin - 1000 * 60 * 60 * 24);
数字类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import java.math.BigDecimal;import java.text.DecimalFormat;public class DecimalFormatTest01 { public static void main (String[] args) { DecimalFormat df = new DecimalFormat ("###,###.##" ); String s = df.format(1234.561232 ); System.out.println(s); DecimalFormat df2 = new DecimalFormat ("###,###.0000" ); String s2 = df2.format(1234.56 ); System.out.println(s2); } } public class BigDecimalTest01 { public static void main (String[] args) { BigDecimal v1 = new BigDecimal (100 ); BigDecimal v2 = new BigDecimal (200 ); BigDecimal v3 = v1.add(v2); System.out.println(v3); BigDecimal v4 = v2.divide(v1); System.out.println(v4); } }
###,###.## 表示加入千分位,保留两个小数。 ###,###.0000 表示加入千分位,保留4个小数,不够补0
财务软件中通常使用BigDecimal
随机数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class RandomTest01 { public static void main (String[] args) { Random random = new Random (); int num1 = random.nextInt(); System.out.println(num1); int num2 = random.nextInt(101 ); System.out.println(num2); } }
Random r = new Random(); int i = r.nextInt();
Random r = new Random(); int i = r.nextInt(101); // 产生[0-100]的随机数。
枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 public class SwitchTest { public static void main (String[] args) { switch (Season.SPRING) { case SPRING: System.out.println("春天" ); break ; case SUMMER: System.out.println("夏天" ); break ; case AUTUMN: System.out.println("秋天" ); break ; case WINTER: System.out.println("冬天" ); break ; } } } package com.bjpowernode.javase.enum2; public class EnumTest01 { public static void main (String[] args) { boolean success = divide(10 , 0 ); System.out.println(success ? "计算成功" : "计算失败" ); } public static boolean divide (int a, int b) { try { int c = a / b; return true ; } catch (Exception e){ return false ; } } } public class EnumTest02 { public static void main (String[] args) { Result r = divide(10 , 2 ); System.out.println(r == Result.SUCCESS ? "计算成功" : "计算失败" ); } public static Result divide (int a, int b) { try { int c = a / b; return Result.SUCCESS; } catch (Exception e){ return Result.FAIL; } } } enum Result { SUCCESS, FAIL }
枚举是一种引用数据类型。
枚举编译之后也是class文件。
枚举类型怎么定义?
enum 枚举类型名{
枚举值,枚举值2,枚举值3 }
当一个方法执行结果超过两种情况,并且是一枚一枚可以列举出来
的时候,建议返回值类型设计为枚举类型。
集合 集合 什么是集合?有什么用?
数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。
集合为什么说在开发中使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
注意:
集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”。
在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:数组、二叉树、链表、哈希表…以上这些都是常见的数据结构。
new ArrayList(); 创建一个集合,底层是数组。 new LinkedList(); 创建一个集合对象,底层是链表。 new TreeSet(); 创建一个集合对象,底层是二叉树。 …..
集合在java JDK中哪个包下?
java.util.*;所有的集合类和集合接口都在java.util包下。
为了让大家掌握集合这块的内容,最好能将集合的继承结构图背会!!!
在java中集合分为两大类:
一类是单个方式存储元素:单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
一类是以键值对儿的方式存储元素以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
重点:
第一个重点:把集合继承结构图背会。
第二个重点:把Collection接口中常用方法测试几遍。
第三个重点:把迭代器弄明白。
第四个重点:Collection接口中的remove方法和contains方法底层都会调用equals, 这个弄明白。
总结:所有实现类
ArrayList 底层是数组
LinkedList 底层是双向链表
Vector 底层是数组,线程安全,效率比较低,使用较少
HashSet 底层是HashMap,放在HashSet集合中的元素等同于放到HashMap集合key部分了
TreeSet 底层是TreeMap,放在TreeSet集合中的元素等同于放到TreeMap集合的key部分了
HashMap 底层是哈希表
HashTable 底层也是哈希表,不过是线程安全的,效率较低,使用较少
Properties 是线程安全的,并且k和v只能存储字符串String
TreeMap 底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序
List 集合存储元素特点
有序可重复
有序:存进去的顺序和取出得到顺序相同,每一个元素都有下标
可重复:存进去1, 可以再存储一个1
Set(Map) 集合存储元素特点
无序不可重复
无序:存进去的顺序和去除的顺序不一定相同,另外Set集合中没有下标
不可重复:存进去1,不能再存储1了
SortedSet(SortedMap) 集合存储元素特点
无序不可重复但是可以排序
可排序:可以按照大小顺序排列
Map集合的Key就是一个Set集合,往Set中放数据就相当于放到了map的key部分
Collection 关于java.util.Collection接口中常用的方法。
Collection中能存放什么元素?
没有使用“泛型”之前,Collection中可以存储Object的所有子类型,且可以不同 ,因为存的是地址
使用了“泛型”之后,Collection中只能存储某个具体的类型。
Collection中什么都能存只要是Object的子类型就行。
集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。
Collection中的常用方法
boolean add(Object e) 向集合中添加元素(自动装箱)
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】
迭代器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class CollectionTest02 { public static void main (String[] args) { Collection c = new ArrayList (); c.add("abc" ); c.add(new Object ()); Iterator it = c.iterator(); while (it.hasNext()){ Object obj = it.next(); System.out.println(obj); } while (it2.hasNext()){ System.out.println(it2.next()); } } }
contains 深入Collection集合的contains方法:
boolean contains(Object o) 判断集合中是否包含某个对象o 如果包含返回true, 如果不包含返回false。
contains方法是用来判断集合中是否包含某个元素的方法,那么它在底层是怎么判断集合中是否包含某个元素的呢? 调用了equals方法进行比对。equals方法返回true,就表示包含这个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class CollectionTest04 { public static void main (String[] args) { Collection c = new ArrayList (); String s1 = new String ("abc" ); c.add(s1); String s2 = new String ("def" ); c.add(s2); System.out.println("元素的个数是:" + c.size()); String x = new String ("abc" ); System.out.println(c.contains(x)); } }
结论:放在集合中的元素一定要重写equals方法
remove
当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModificationException
在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:c.remove(o); 迭代过程中不能这样。会出现:java.util.ConcurrentModificationException
在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素不要使用集合自带的remove方法删除元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class CollectionTest06 { public static void main (String[] args) { Collection c = new ArrayList (); Iterator it = c.iterator(); c.add(1 ); c.add(2 ); Collection c2 = new ArrayList (); c2.add("abc" ); c2.add("def" ); c2.add("xyz" ); Iterator it2 = c2.iterator(); while (it2.hasNext()){ Object o = it2.next(); it2.remove(); System.out.println(o); } System.out.println(c2.size()); } }
List List集合存储元素特点:有序可重复
有序:List集合中的元素有下标。
从0开始,以1递增。
可重复:存储一个1,还可以再存储1.
List既然是Collection接口的子接口,那么肯定List接口有自己“特色”的方法: 以下只列出List接口特有的常用的方法:
void add(int index, Object element)
Object set(int index, Object element)
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)
以上几个方法不需要死记硬背,可以自己编写代码测试一下,理解一下, 以后开发的时候,还是要翻阅帮助文档。
迭代器迭代元素的过程中不能使用集合对象的remove方法删除元素,要使用迭代器Iterator的remove方法来删除元素,防止出现异常: ConcurrentModificationException
怎么得到一个线程安全的List: Collections.synchronizedList(list);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class ListTest01 { public static void main (String[] args) { List myList = new ArrayList (); myList.add("A" ); myList.add("B" ); myList.add(1 , "KING" ); Iterator it = myList.iterator(); while (it.hasNext()){ Object elt = it.next(); System.out.println(elt); } Object firstObj = myList.get(0 ); System.out.println(firstObj); for (int i = 0 ; i < myList.size(); i++){ Object obj = myList.get(i); System.out.println(obj); } System.out.println(myList.indexOf("C" )); System.out.println(myList.lastIndexOf("C" )); myList.remove(0 ); myList.set(2 , "Soft" ); for (int i = 0 ; i < myList.size(); i++){ Object obj = myList.get(i); System.out.println(obj); } } }
ArrayList
默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)扩容为原容量1.5倍。
集合底层是一个Object[]数组。
构造方法:
new ArrayList();
new ArrayList(20);
ArrayList集合的扩容: 增长到原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
向数组末尾添加元素,效率很高,不受影响。
面试官经常问的一个问题? 这么多的集合中,你用哪个集合最多?
答:ArrayList集合。因为往数组末尾添加元素,效率不受影响。另外,我们检索/查找某个元素的操作比较多。
ArrayList集合是非线程安全的。(不是线程安全的集合。)
ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
怎么将一个线程不安全的ArrayList集合转换成线程安全的呢? 使用集合工具类:java.util.Collections;
java.util.Collection 是集合接口。
java.util.Collections 是集合工具类。
LinkedList 链表的优点: 由于链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。在以后的开发中,如果遇到随机增删集合中元素 的业务比较多时,建议使用LinkedList。
链表的缺点: 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率较低。
LinkedList集合底层也是有下标的。
LinkedList集合有初始化容量吗?没有。
最初这个链表中没有任何元素。first和last引用都是null。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。) LinkedList:把随机增删发挥到极致。 加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
Vector Vector初始化容量是10.
扩容为原容量的2倍。
底层是数组。
Vector底层是线程安全的。
Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。效率比较低,使用较少了。
泛型 使用泛型
JDK5.0之后推出的新特性:泛型
泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)
使用了泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!
泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class GenericTest01 { public static void main (String[] args) { List<Animal> myList = new ArrayList <Animal>(); Cat c = new Cat (); Bird b = new Bird (); myList.add(c); myList.add(b); Iterator<Animal> it = myList.iterator(); while (it.hasNext()){ Animal a = it.next(); if (a instanceof Cat) { Cat x = (Cat)a; x.catchMouse(); } if (a instanceof Bird) { Bird y = (Bird)a; y.fly(); } } } } class Animal { public void move () { System.out.println("动物在移动!" ); } } class Cat extends Animal { public void catchMouse () { System.out.println("猫抓老鼠!" ); } } class Bird extends Animal { public void fly () { System.out.println("鸟儿在飞翔!" ); } }
钻石表达式(自动类型推断) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class GenericTest02 { public static void main (String[] args) { List<Animal> myList = new ArrayList <>(); myList.add(new Animal ()); myList.add(new Cat ()); myList.add(new Bird ()); Iterator<Animal> it = myList.iterator(); while (it.hasNext()){ Animal a = it.next(); a.move(); } List<String> strList = new ArrayList <>(); Iterator<String> it2 = strList.iterator(); while (it2.hasNext()){ String s = it2.next(); String newString = s.substring(7 ); System.out.println(newString); } } }
自定义泛型 //不太明白这个
自定义泛型可以吗?可以
自定义泛型的时候,<> 尖括号中的是一个标识符,随便写。
java源代码中经常出现的是: 和
E是Element单词首字母。
T是Type单词首字母。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class GenericTest03 <标识符随便写> { public void doSome (标识符随便写 o) { System.out.println(o); } public static void main (String[] args) { GenericTest03<String> gt = new GenericTest03 <>(); gt.doSome("abc" ); GenericTest03<Integer> gt2 = new GenericTest03 <>(); gt2.doSome(100 ); MyIterator<String> mi = new MyIterator <>(); String s1 = mi.get(); MyIterator<Animal> mi2 = new MyIterator <>(); Animal a = mi2.get(); } } class MyIterator <T> { public T get () { return null ; } }
Map
Set set = map1.entrySet(); set集合对象 1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】 2=lisi【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】 3=wangwu 4=zhaoliu —> 这个东西是个什么? Map.Entry
使用方式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MapTest01 { public static void main (String[] args) { Map<Integer, String> map = new HashMap <>(); map.put(1 , "zhangsan" ); map.put(2 , "lisi" ); map.put(3 , "wangwu" ); String value = map.get(2 ); System.out.println(value); System.out.println("键值对的数量:" + map.size()); map.remove(2 ); System.out.println(map.containsKey(new Integer (4 ))); System.out.println(map.containsValue(new String ("wangwu" ))); Collection<String> values = map.values(); for (String s : values){ System.out.println(s); } map.clear(); System.out.println("键值对的数量:" + map.size()); System.out.println(map.isEmpty()); } }
遍历方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class MapTest02 { public static void main (String[] args) { Map<Integer, String> map = new HashMap <>(); map.put(1 , "zhangsan" ); map.put(2 , "lisi" ); map.put(3 , "wangwu" ); map.put(4 , "zhaoliu" ); Set<Integer> keys = map.keySet(); for (Integer key : keys){ System.out.println(key + "=" + map.get(key)); } Set<Map.Entry<Integer,String>> set = map.entrySet(); for (Map.Entry<Integer,String> node : set){ System.out.println(node.getKey() + "--->" + node.getValue()); } } }
哈希表(散列表) HashMap集合:
HashMap集合底层是哈希表/散列表的数据结构。
哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率较高,在查询方面效率很低。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。”中庸”
HashMap集合底层的源代码: 1 2 3 4 5 6 7 8 9 10 11 public class HashMap { Node<K,V>[] table; static class Node <K,V> { final int hash; final K key; V value; Node<K,V> next; } }
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理,是必须掌握的。
HashMap集合的key部分特点:
无序,不可重复。
为什么无序? 因为不一定挂到哪个单向链表上。
不可重复是怎么保证的? equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀。
什么是散列分布均匀?
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,是散列分布均匀的。
假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。也是散列分布不均匀。散列分布均匀需要你重写hashCode()方法时有一定的技巧。
重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法.
HashMap集合的默认初始化容量是16,默认加载因子是0.75, 这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
重点,记住:HashMap集合初始化容量必须是2的倍数 ,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
这里有个图
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class HashMapTest01 { public static void main (String[] args) { Map<Integer,String> map = new HashMap <>(); map.put(1111 , "zhangsan" ); map.put(6666 , "lisi" ); map.put(7777 , "wangwu" ); map.put(2222 , "zhaoliu" ); map.put(2222 , "king" ); System.out.println(map.size()); Set<Map.Entry<Integer,String>> set = map.entrySet(); for (Map.Entry<Integer,String> entry : set){ System.out.println(entry.getKey() + "=" + entry.getValue()); } } }
必须重写hashcode和equals
向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法! equals方法有可能调用,也有可能不调用。
拿put(k,v)举例,什么时候equals不会调用?k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
拿get(k)举例,什么时候equals不会调用?k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。数组下标位置上如果是null,equals不需要执行。
注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是true,hashCode()方法返回的值必须一样。equals方法返回true表示两个对象相同,在同一个单向链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。所以hashCode()方法的返回值也应该相同。
hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。
对于哈希表数据结构来说:如果o1和o2的hash值相同,一定是放到同一个单向链表上。 如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
HashMap集合key部分允许null吗? 允许 但是要注意:HashMap集合的key null值只能有一个。
终极结论:放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法 。
HashMap和Hashtable的区别。 Hashtable和HashMap一样,底层都是哈希表数据结构。
HashMap:
初始化容量16,扩容2倍。非线程安全。key和value可以为null。
HashMap集合的key和value都是可以为null的。
Hashtable:
初始化容量11,默认加载因子是:0.75f,扩容2倍+1。线程安全。key和value都不能是null。
Hashtable的key和value都是不能为null的。
Hashtable方法都带有synchronized:线程安全的。线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,使用较少了。
Properties 目前只需要掌握Properties属性类对象的相关方法即可。 Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。 Properties被称为属性类对象。 Properties是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class PropertiesTest01 { public static void main (String[] args) { Properties pro = new Properties (); pro.setProperty("url" , "jdbc:mysql://localhost:3306/bjpowernode" ); pro.setProperty("driver" ,"com.mysql.jdbc.Driver" ); String url = pro.getProperty("url" ); String driver = pro.getProperty("driver" ); System.out.println(url); System.out.println(driver); } }
TreeMap/TreeSet TreeSet集合存储元素特点:
无序不可重复的,但是存储的元素可以自动按照大小顺序排序!称为:可排序集合。
无序:这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
TreeSet集合底层实际上是一个TreeMap
TreeMap集合底层是一个二叉树。
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
无法对自定义类型排序castException
可以不写equals方法,因为底层调用了comparable方法进行比较了
自定义排序规则 TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式:
第一种:实现java.lang.Comparable接口。
第二种:单独编写一个比较器Comparator接口。
第一种 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class TreeSetTest04 { public static void main (String[] args) { Customer c1 = new Customer (32 ); Customer c2 = new Customer (20 ); TreeSet<Customer> customers = new TreeSet <>(); customers.add(c1); customers.add(c2); for (Customer c : customers){ System.out.println(c); } } } class Customer implements Comparable <Customer>{ int age; public Customer (int age) { this .age = age; } @Override public int compareTo (Customer c) { return c.age - this .age; } public String toString () { return "Customer[age=" +age+"]" ; } @Override public int compareTo (Vip v) { if (this .age == v.age){ return this .name.compareTo(v.name); } else { return this .age - v.age; } } }
第二种 使用比较器的方式
最终的结论:放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式 - 第一种:放在集合中的元素实现java.lang.Comparable接口。 - 第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class TreeSetTest06 { public static void main (String[] args) { TreeSet<WuGui> wuGuis = new TreeSet <>(new Comparator <WuGui>() { @Override public int compare (WuGui o1, WuGui o2) { return o1.age - o2.age; } }); wuGuis.add(new WuGui (1000 )); wuGuis.add(new WuGui (800 )); wuGuis.add(new WuGui (810 )); for (WuGui wuGui : wuGuis){ System.out.println(wuGui); } } } class WuGui { int age; public WuGui (int age) { this .age = age; } @Override public String toString () { return "小乌龟[" + "age=" + age + ']' ; } } class WuGuiComparator implements Comparator <WuGui> { @Override public int compare (WuGui o1, WuGui o2) { return o1.age - o2.age; } }
自平衡二叉树 图片
Collections工具类 synchronizedList方法
sort方法(要求集合中元素实现Comparable接口。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class CollectionsTest { public static void main (String[] args) { List<String> list = new ArrayList <>(); Collections.synchronizedList(list); list.add("abf" ); list.add("abx" ); Collections.sort(list); for (String s : list){ System.out.println(s); } List<WuGui2> wuGuis = new ArrayList <>(); wuGuis.add(new WuGui2 (1000 )); wuGuis.add(new WuGui2 (8000 )); Collections.sort(wuGuis); for (WuGui2 wg : wuGuis){ System.out.println(wg); } Set<String> set = new HashSet <>(); set.add("king" ); set.add("kingsoft" ); List<String> myList = new ArrayList <>(set); Collections.sort(myList); for (String s : myList) { System.out.println(s); } } } class WuGui2 implements Comparable <WuGui2>{ int age; public WuGui2 (int age) { this .age = age; } @Override public int compareTo (WuGui2 o) { return this .age - o.age; } @Override public String toString () { return "WuGui2{" + "age=" + age + '}' ; } }
异常 异常处理 是什么 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 public class ExceptionTest01 { public static void main (String[] args) { int a = 10 ; int b = 0 ; int c = a / b; System.out.println(a + "/" + b + "=" + c); } } public class ExceptionTest02 { public static void main (String[] args) { NullPointerException npe = new NullPointerException ("空指针异常发生了!" ); System.out.println(npe); } } public class ExceptionTest03 { public static void main (String[] args) { System.out.println(100 / 0 ); System.out.println("Hello World!" ); } } public class ExceptionTest04 { public static void main (String[] args) { } public static void doSome () throws ClassNotFoundException{ System.out.println("doSome!!!!" ); } }
java中异常的作用是:增强程序健壮性。
java中异常以类和对象的形式存在。
Object Object下有Throwable(可抛出的) Throwable下有两个分支:
Error(不可处理,直接退出JVM)
Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名? 因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。 编译时异常和运行时异常的区别?编译时异常一般发生的概率比较高。 举个例子: 你看到外面下雨了,倾盆大雨的。你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。“拿一把伞”就是对“生病异常”发生之前的一种处理方式。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。 举个例子: 小明走在大街上,可能会被天上的飞机轮子砸到。被飞机轮子砸到也算一种异常。但是这种异常发生概率较低。在出门之前你没必要提前对这种发生概率较低的异常进行预处理。如果你预处理这种异常,你将活的很累。
假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加的安全,但是你这个人活的很累。
假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?首先,如果这样的话,程序肯定是绝对的安全的。 但是程序员编写程序太累,代码到处都是处理异常的代码。
编译时异常还有其他名字:
受检异常:CheckedException
受控异常 运行时异常还有其它名字:
未受检异常:UnCheckedException
非受控异常
再次强调:所有异常都是发生在运行阶段的。
怎样处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 public class ExceptionTest05 { public static void main (String[] args) { try { doSome(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static void doSome () throws ClassNotFoundException{ System.out.println("doSome!!!!" ); } } import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class ExceptionTest06 { public static void main (String[] args) { System.out.println("main begin" ); try { m1(); System.out.println("hello world!" ); } catch (FileNotFoundException e){ System.out.println("文件不存在,可能路径错误,也可能该文件被删除了!" ); System.out.println(e); } System.out.println("main over" ); } private static void m1 () throws FileNotFoundException { System.out.println("m1 begin" ); m2(); System.out.println("m1 over" ); } private static void m2 () throws FileNotFoundException { System.out.println("m2 begin" ); m3(); System.out.println("m2 over" ); } private static void m3 () throws FileNotFoundException { new FileInputStream ("D:\\course\\01-课\\学习方法.txt" ); System.out.println("如果以上代码出异常,这里会执行吗??不会!!!" ); } }
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try..catch语句进行异常的捕捉。这件事发生了,谁也不知道,因为我给抓住了。
举个例子: 我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,“损失1000元”这可以看做是一个异常发生了。我有两种处理方式,第一种方式:我把这件事告诉我的领导【异常上抛】第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】
张三 –> 李四 —> 王五 –> CEO
思考: 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
深入理解try cache机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class ExceptionTest07 { public static void main (String[] args) { try { FileInputStream fis = new FileInputStream ("\JavaSE进阶-01-面向对象.pdf" ); fis.read(); } catch (IOException e){ System.out.println("读文件报错了!" ); } catch (FileNotFoundException e) { System.out.println("文件不存在!" ); } try { FileInputStream fis = new FileInputStream ("\JavaSE进阶-01-面向对象.pdf" ); System.out.println(100 / 0 ); } catch (FileNotFoundException | ArithmeticException | NullPointerException e) { System.out.println("文件不存在?数学异常?空指针异常?都有可能!" ); } } }
异常对象的常用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 import java.io.FileInputStream;import java.io.FileNotFoundException;public class ExceptionTest08 { public static void main (String[] args) { NullPointerException e = new NullPointerException ("空指针异常" ); String msg = e.getMessage(); e.printStackTrace(); } } public class ExceptionTest09 { public static void main (String[] args) { try { m1(); } catch (FileNotFoundException e) { String msg = e.getMessage(); System.out.println(msg); } System.out.println("Hello World!" ); } private static void m1 () throws FileNotFoundException { m2(); } private static void m2 () throws FileNotFoundException { m3(); } private static void m3 () throws FileNotFoundException { new FileInputStream ("C:\\jetns-agent.jar" ); } }
finally子句的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class ExceptionTest10 { public static void main (String[] args) { FileInputStream fis = null ; try { fis = new FileInputStream ("D:\\course\\02-JavaSE\\document\\JavaSE进阶讲义\\JavaSE进阶-01-面向对象.pdf" ); String s = null ; s.toString(); System.out.println("hello world!" ); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e){ e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } finally { System.out.println("hello Finally !" ); if (fis != null ) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } } public class ExceptionTest11 { public static void main (String[] args) { try { System.out.println("try..." ); return ; } finally { System.out.println("finally..." ); } } } public class ExceptionTest12 { public static void main (String[] args) { try { System.out.println("try..." ); System.exit(0 ); } finally { System.out.println("finally..." ); } } } public class ExceptionTest13 { public static void main (String[] args) { int result = m(); System.out.println(result); } public static int m () { int i = 100 ; try { return i; } finally { i++; } } }
finally finalize final 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class ExceptionTest14 { public static void main (String[] args) { final int i = 100 ; try { } finally { System.out.println("finally...." ); } Object obj; } } final class A { public static final double MATH_PI = 3.1415926 ; } class B { public final void doSome () { } }
自定义异常 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class MyException extends Exception { public MyException () { } public MyException (String s) { super (s); } } public class ExceptionTest15 { public static void main (String[] args) { MyException e = new MyException ("用户名不能为空!" ); e.printStackTrace(); String msg = e.getMessage(); System.out.println(msg); } }
例题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class MyStack { private Object[] elements; public void push (Object obj) throws MyStackOperationException { if (index >= elements.length - 1 ){ throw new MyStackOperationException ("压栈失败,栈已满!" ); } } public void pop () throws MyStackOperationException { if (index < 0 ){ throw new MyStackOperationException ("弹栈失败,栈已空!" ); } } public class MyStackOperationException extends Exception { public MyStackOperationException () { } public MyStackOperationException (String s) { super (s); } }
IO IO流 IDEA的默认当前路径是在哪?工程的根目录
有多种分类方式:
一种方式是按照流的方向进行分类:以内存作为参照物
往内存中去,叫做输入(Input)。或者叫做读(Read)。
从内存中出来,叫做输出(Output)。或者叫做写(Write)。
另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等…. 假设文件file1.txt,采用字节流的话是这样读的: a中国bc张三fe 第一次读:一个字节,正好读到’a’ 第二次读:一个字节,正好读到’中’字符的一半。 第三次读:一个字节,正好读到’中’字符的另外一半。
有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。 假设文件file1.txt,采用字符流的话是这样读的: a中国bc张三fe 第一次读:’a’字符(’a’字符在windows系统中占用1个字节。) 第二次读:’中’字符(’中’字符在windows系统中占用2个字节。)
流的分类
java中所有的流都是在:java.io.*;下。
java中主要还是研究: 怎么new流对象。调用流对象的哪个方法是读,哪个方法是写。
java IO流这块有四大家族: 四大家族的首领: 四大家族的首领都是抽象类。(abstract class)
所有的流 都实现了: java.io.Closeable接口,都是可关闭的,都有close()方法。 流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。
所有的输出流 都实现了: java.io.Flushable接口,都是可刷新的,都有flush()方法。 养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。注意:如果没有flush()可能会导致丢失数据。
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。
java.io包下需要掌握的流有16个:
文件专属: java.io.FileInputStream(掌握) java.io.FileOutputStream(掌握) java.io.FileReader java.io.FileWriter
转换流:(将字节流转换成字符流) java.io.InputStreamReader java.io.OutputStreamWriter
缓冲流专属: java.io.BufferedReader java.io.BufferedWriter java.io.BufferedInputStream java.io.BufferedOutputStream
数据流专属: java.io.DataInputStream java.io.DataOutputStream
标准输出流: java.io.PrintWriter java.io.PrintStream(掌握)
对象专属流: java.io.ObjectInputStream(掌握) java.io.ObjectOutputStream(掌握)
java.io.File类。 File类的常用方法。
java io这块还剩下什么内容: 第一:ObjectInputStream ObjectOutputStream的使用。 第二:IO流+Properties集合的联合使用。
1、文件字节输入流,万能的,任何类型的文件都可以采用这个流来读。
2、字节的方式,完成输入的操作,完成读的操作(硬盘—> 内存)
最终版:背模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class FileInputStreamTest04 { public static void main (String[] args) { FileInputStream fis = null ; try { fis = new FileInputStream ("chapter23/src/tempfile3" ); byte [] bytes = new byte [4 ]; int readCount = 0 ; while ((readCount = fis.read(bytes)) != -1 ) { System.out.print(new String (bytes, 0 , readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null ) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileInputStream类的其它常用方法:
int available():返回流当中剩余的没有读到的字节数量
long skip(long n):跳过几个字节不读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class FileInputStreamTest05 { public static void main (String[] args) { FileInputStream fis = null ; try { fis = new FileInputStream ("tempfile" ); System.out.println("总字节数量:" + fis.available()); fis.skip(3 ); System.out.println(fis.read()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fis != null ) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileOutputStream 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class FileOutputStreamTest01 { public static void main (String[] args) { FileOutputStream fos = null ; try { fos = new FileOutputStream ("chapter23/src/tempfile3" , true ); byte [] bytes = {97 , 98 , 99 , 100 }; fos.write(bytes); fos.write(bytes, 0 , 2 ); String s = "我是一个中国人,我骄傲!!!" ; byte [] bs = s.getBytes(); fos.write(bs); fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null ) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileReader
文件字符输入流,只能读取普通文本。
读取文本内容时,比较方便,快捷。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class FileReaderTest { public static void main (String[] args) { FileReader reader = null ; try { reader = new FileReader ("tempfile" ); char [] chars = new char [4 ]; char [] chars = new char [4 ]; int readCount = 0 ; while ((readCount = reader.read(chars)) != -1 ) { System.out.print(new String (chars,0 ,readCount)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null ) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileWriter
文件字符输出流。写。
只能输出普通文本,普通文本,不能输出图片声音等文件
能用记事本编辑的都是普通文件
word不是普通文本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class FileWriterTest { public static void main (String[] args) { FileWriter out = null ; try { out = new FileWriter ("file" , true ); char [] chars = {'我' ,'是' ,'中' ,'国' ,'人' }; out.write(chars); out.write(chars, 2 , 3 ); out.write("我是一名java软件工程师!" ); out.write("\n" ); out.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null ) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
BufferedReader
这个流没有前面那些快
带有缓冲区的字符输入流。
使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class BufferedReaderTest01 { public static void main (String[] args) throws Exception{ FileReader reader = new FileReader ("Copy02.java" ); BufferedReader br = new BufferedReader (reader); String s = null ; while ((s = br.readLine()) != null ){ System.out.print(s); } br.close(); } }
BufferedWriter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class BufferedWriterTest { public static void main (String[] args) throws Exception{ BufferedWriter out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream ("copy" , true ))); out.write("hello world!" ); out.flush(); out.close(); } }
转换流(少用) 把字节流转换为字符流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class BufferedReaderTest02 { public static void main (String[] args) throws Exception{ BufferedReader br = new BufferedReader (new InputStreamReader (new FileInputStream ("Copy02.java" ))); String line = null ; while ((line = br.readLine()) != null ){ System.out.println(line); } br.close(); } }
数据流(了解)
java.io.DataOutputStream:数据专属的流。
这个流可以将数据连同数据的类型一并写入文件。
注意:这个文件不是普通文本文档。(这个文件使用记事本打不开 。)
写入文件之后,相当于“加密了”。 用普通的记事本文件打开是乱码,在“解密”的时候,必须按照一样的顺序 读入流中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class DataOutputStreamTest { public static void main (String[] args) throws Exception{ DataOutputStream dos = new DataOutputStream (new FileOutputStream ("data" )); byte b = 100 ; short s = 200 ; int i = 300 ; long l = 400L ; float f = 3.0F ; double d = 3.14 ; boolean sex = false ; char c = 'a' ; dos.writeByte(b); dos.writeShort(s); dos.writeInt(i); dos.writeLong(l); dos.writeFloat(f); dos.writeDouble(d); dos.writeBoolean(sex); dos.writeChar(c); dos.flush(); dos.close(); } }
DataInputStream:数据字节输入流。
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class DataInputStreamTest01 { public static void main (String[] args) throws Exception{ DataInputStream dis = new DataInputStream (new FileInputStream ("data" )); byte b = dis.readByte(); short s = dis.readShort(); int i = dis.readInt(); long l = dis.readLong(); float f = dis.readFloat(); double d = dis.readDouble(); boolean sex = dis.readBoolean(); char c = dis.readChar(); System.out.println(b); System.out.println(s); System.out.println(i + 1000 ); System.out.println(l); System.out.println(f); System.out.println(d); System.out.println(sex); System.out.println(c); dis.close(); } }
标准输出流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class PrintStreamTest { public static void main (String[] args) throws Exception{ System.out.println("hello world!" ); PrintStream ps = System.out; ps.println("hello zhangsan" ); PrintStream printStream = new PrintStream (new FileOutputStream ("log" )); System.setOut(printStream); System.out.println("hello world" ); } }
记录日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Logger { public static void log (String msg) { try { PrintStream out = new PrintStream (new FileOutputStream ("log.txt" , true )); System.setOut(out); Date nowTime = new Date (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss SSS" ); String strTime = sdf.format(nowTime); System.out.println(strTime + ": " + msg); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
对象流 java.io.NotSerializableException:
Student对象不支持序列化
参与序列化和反序列化的对象,必须实现Serializable接口。
注意:通过源代码发现,Serializable接口只是一个标志接口:
public interface Serializable {}这个接口当中什么代码都没有。
那么它起到一个什么作用呢?
起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。 Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成 一个序列化版本号。
序列化版本号有什么用呢? java.io.InvalidClassException: com.bjpowernode.java.bean.Student; local class incompatible: stream classdesc serialVersionUID = -684255398724514298(十年后), local class serialVersionUID = -3463447116624555755(十年前)
java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。
小鹏编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
胡浪编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了:对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)
请思考?
这种自动生成序列化版本号有什么缺陷?
这种自动生成的序列化版本号缺点是:一旦代码确定之后,不能进行后续的修改,
因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java
虚拟机会认为这是一个全新的类。(这样就不好了!)
最终结论:
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
private static final long serialVersionUID = -123456789;(自己指定)
IDEA生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 public class ObjectOutputStreamTest01 { public static void main (String[] args) throws Exception{ Student s = new Student (1111 , "zhangsan" ); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("students" )); oos.writeObject(s); oos.flush(); oos.close(); } } public class ObjectOutputStreamTest02 { public static void main (String[] args) throws Exception{ List<User> userList = new ArrayList <>(); userList.add(new User (1 ,"zhangsan" )); userList.add(new User (2 , "lisi" )); userList.add(new User (3 , "wangwu" )); ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("users" )); oos.writeObject(userList); oos.flush(); oos.close(); } } public class ObjectInputStreamTest01 { public static void main (String[] args) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("students" )); Object obj = ois.readObject(); System.out.println(obj); ois.close(); } } public class ObjectInputStreamTest02 { public static void main (String[] args) throws Exception{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream ("users" )); List<User> userList = (List<User>)ois.readObject(); for (User user : userList){ System.out.println(user); } ois.close(); } }
transient 关键字,指定的变量不参加序列化
IO + properties 联合使用 IO+Properties的联合应用。
非常好的一个设计理念:
java规范中有要求:
属性配置文件建议以.properties结尾,但这不是必须的。
这种以.properties结尾的文件在java中被称为:属性配置文件。
其中Properties是专门存放属性配置文件内容的一个类。
最好不要有空格
在属性配置文件中, # 为注释,重复的值会覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class IoPropertiesTest01 { public static void main (String[] args) throws Exception{ FileReader reader = new FileReader ("chapter23/userinfo.properties" ); Properties pro = new Properties (); pro.load(reader); String username = pro.getProperty("username" ); System.out.println(username); } }
文件复制
使用FileInputStream + FileOutputStream完成文件的拷贝。
拷贝的过程应该是一边读,一边写。
使用以上的字节流拷贝文件的时候,文件类型随意,万能的。什么样的文件都能拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Copy01 { public static void main (String[] args) { FileInputStream fis = null ; FileOutputStream fos = null ; try { fis = new FileInputStream ("D:\\course\\02-JavaSE\\video\\chapter01\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi" ); fos = new FileOutputStream ("C:\\动力节点-JavaSE-杜聚宾-001-文件扩展名的显示.avi" ); byte [] bytes = new byte [1024 * 1024 ]; int readCount = 0 ; while ((readCount = fis.read(bytes)) != -1 ) { fos.write(bytes, 0 , readCount); } fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null ) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null ) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
FileReader + FileWriter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Copy02 { public static void main (String[] args) { FileReader in = null ; FileWriter out = null ; try { in = new FileReader ("chapter23/src/com/bjpowernode/java/io/Copy02.java" ); out = new FileWriter ("Copy02.java" ); char [] chars = new char [1024 * 512 ]; int readCount = 0 ; while ((readCount = in.read(chars)) != -1 ){ out.write(chars, 0 , readCount); } out.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null ) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null ) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
目录拷贝 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class CopyAll { public static void main (String[] args) { File srcFile = new File ("D:\\course\\02-JavaSE\\document" ); File destFile = new File ("C:\\a\\b\\c" ); copyDir(srcFile, destFile); } private static void copyDir (File srcFile, File destFile) { if (srcFile.isFile()) { FileInputStream in = null ; FileOutputStream out = null ; try { in = new FileInputStream (srcFile); String path = (destFile.getAbsolutePath().endsWith("\\" ) ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\" ) + srcFile.getAbsolutePath().substring(3 ); out = new FileOutputStream (path); byte [] bytes = new byte [1024 * 1024 ]; int readCount = 0 ; while ((readCount = in.read(bytes)) != -1 ){ out.write(bytes, 0 , readCount); } out.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null ) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null ) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } return ; } File[] files = srcFile.listFiles(); for (File file : files){ if (file.isDirectory()){ String srcDir = file.getAbsolutePath(); String destDir = (destFile.getAbsolutePath().endsWith("\\" ) ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\" ) + srcDir.substring(3 ); File newFile = new File (destDir); if (!newFile.exists()){ newFile.mkdirs(); } } copyDir(file, destFile); } } }
File类 File类和四大家族没有关系,所以File类不能完成文件的读和写。 File对象代表什么?
- 文件和目录路径名的抽象表示形式。
- C:\Drivers 这是一个File对象
- C:\Drivers\Lan\Realtek\Readme.txt 也是File对象。
- 一个File对象有可能对应的是目录,也可能是文件。
- File只是一个路径名的抽象表示形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class FileTest01 { public static void main (String[] args) throws Exception { File f1 = new File ("D:\\file" ); System.out.println(f1.exists()); if (!f1.exists()) { f1.createNewFile(); } if (!f1.exists()) { f1.mkdir(); } File f2 = new File ("D:/a/b/c/d/e/f" ); if (!f2.exists()) { f2.mkdirs(); } File f3 = new File ("D:\\course\\01-开课\\学习方法.txt" ); String parentPath = f3.getParent(); System.out.println(parentPath); File parentFile = f3.getParentFile(); System.out.println("获取绝对路径:" + parentFile.getAbsolutePath()); File f4 = new File ("copy" ); System.out.println("绝对路径:" + f4.getAbsolutePath()); } } public class FileTest02 { public static void main (String[] args) { File f1 = new File ("D:\\course\\01-开课\\开学典礼.ppt" ); System.out.println("文件名:" + f1.getName()); System.out.println(f1.isDirectory()); System.out.println(f1.isFile()); long haoMiao = f1.lastModified(); Date time = new Date (haoMiao); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss SSS" ); String strTime = sdf.format(time); System.out.println(strTime); System.out.println(f1.length()); } } public class FileTest03 { public static void main (String[] args) { File f = new File ("D:\\course\\01-开课" ); File[] files = f.listFiles(); for (File file : files){ System.out.println(file.getName()); } } }
Java注解 注解 Annotation
注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件。
怎么自定义注解呢? [修饰符列表] @interface 注解类型名{ }
注解使用时的语法格式是:@注解类型名
注解可以出现在类上、属性上、方法上、变量上等….注解还可以出现在注解类型上。默认情况下,注解可以出现在任意位置。
内置注解 Override 关于JDK lang包下的Override注解 源代码:public @interface Override {}
标识性注解,给编译器做参考的。编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法。如果没有重写,报错。
这个注解只是在编译阶段起作用,和运行期无关!
@Override这个注解只能注解方法。
@Override这个注解是给编译器参考的,和运行阶段没有关系。
凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错。 Override 表示一个方法声明打算重写超类中的另一个方法声明。
Deprecated 作用:告诉其他程序员这个标记的元素已经过时了。
注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。
元注解 什么是元注解?用来标注“注解类型”的“注解”,称为元注解。
常见的元注解有哪些?Target/Retention
关于Target注解:这是一个元注解,用来标注“注解类型”的“注解”这个Target注解用来标注“被标注的注解”可以出现在哪些位置上。
@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上。 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) 表示该注解可以出现在:构造方法上/字段上/局部变量上/方法上/类上…
关于Retention注解:这是一个元注解,用来标注“注解类型”的“注解”这个Retention注解用来标注“被标注的注解”最终保存在哪里。
@Retention(RetentionPolicy.SOURCE):表示该注解只被保留在java源文件中。 @Retention(RetentionPolicy.CLASS):表示该注解被保存在class文件中。 @Retention(RetentionPolicy.RUNTIME):表示该注解被保存在class文件中,并且可以被反射机制所读取。
注解中的属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public @interface MyAnnotation { String name () ; String color () ; int age () default 25 ; } public class MyAnnotationTest { @MyAnnotation(name = "zhangsan", color = "红色") public void doSome () { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class MyAnnotationTest { @MyAnnotation(value = "hehe") public void doSome () { } @MyAnnotation("haha") public void doOther () { } }
需求: 假设有这样一个注解,叫做:@Id
这个注解只能出现在类上面,当这个类上有这个注解的时候,要求这个类中必须有一个int类型的id属性。如果没有这个属性就报异常。如果有这个属性则正常执行!
Java反射 反射机制 反射机制(比较简单,因为只要会查帮助文档,就可以了。)
反射机制有什么用?
反射机制相关的重要的类有哪些?
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
获取一个类的字节码 三种方式
第一种:Class c = Class.forName(“完整类名带包名”);
第二种:Class c = 对象.getClass();
第三种:Class c = 任何类型.class;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class ReflectTest01 { public static void main (String[] args) { Class c1 = null ; Class c2 = null ; try { c1 = Class.forName("java.lang.String" ); c2 = Class.forName("java.util.Date" ); } catch (ClassNotFoundException e) { e.printStackTrace(); } String s = "abc" ; Class x = s.getClass(); Class z = String.class; Class e = double .class; } }
3、关于JDK中自带的类加载器:(聊一聊,不需要掌握,知道当然最好!) 3.1、什么是类加载器? 专门负责加载类的命令/工具。 ClassLoader
3.2、JDK中自带了3个类加载器 启动类加载器:rt.jar 扩展类加载器:ext/*.jar 应用类加载器:classpath
3.3、假设有这样一段代码: String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。 通过类加载器加载,看到以上代码类加载器会找String.class 文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。 注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候, 会通过”扩展类加载器”加载。 注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar
如果“扩展类加载器”没有加载到,那么 会通过“应用类加载器”加载。 注意:应用类加载器专门加载:classpath中的类。
3.4、java中为了保证类加载的安全,使用了双亲委派机制。 优先从启动类加载器中加载,这个称为“父” “父”无法加载到,再从扩展类加载器中加载, 这个称为“母”。双亲委派。如果都加载不到, 才会考虑从应用类加载器中加载。直到加载 到为止。
1、回顾反射机制
1.1、什么是反射机制?反射机制有什么用? 反射机制:可以操作字节码文件 作用:可以让程序更加灵活。
1.2、反射机制相关的类在哪个包下? java.lang.reflect.*;
1.3、反射机制相关的主要的类? java.lang.Class java.lang.reflect.Method; java.lang.reflect.Constructor; java.lang.reflect.Field;
1.4、在java中获取Class的三种方式? 第一种: Class c = Class.forName(“完整类名”); 第二种: Class c = 对象.getClass(); 第三种: Class c = int.class; Class c = String.class;
1.5、获取了Class之后,可以调用无参数构造方法来实例化对象
//c代表的就是日期Date类型 Class c = Class.forName(“java.util.Date”);
//实例化一个Date日期类型的对象 Object obj = c.newInstance();
一定要注意: newInstance()底层调用的是该类型的无参数构造方法。 如果没有这个无参数构造方法会出现”实例化”异常。
1.6、如果你只想让一个类的“静态代码块”执行的话,你可以怎么做? Class.forName(“该类的类名”); 这样类就加载,类加载的时候,静态代码块执行!!!! 在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。
1.7、关于路径问题?
String path = Thread.currentThread().getContextClassLoader() .getResource(“写相对路径,但是这个相对路径从src出发开始找”).getPath();
String path = Thread.currentThread().getContextClassLoader() .getResource(“abc”).getPath();//必须保证src下有abc文件。
String path = Thread.currentThread().getContextClassLoader() .getResource(“a/db”).getPath();//必须保证src下有a目录,a目录下有db文件。
String path = Thread.currentThread().getContextClassLoader() .getResource(“com/bjpowernode/test.properties”).getPath(); //必须保证src下有com目录,com目录下有bjpowernode目录。 //bjpowernode目录下有test.properties文件。
这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。) 但是该文件要求放在类路径下,换句话说:也就是放到src下面。 src下是类的根路径。
直接以流的形式返回: InputStream in = Thread.currentThread().getContextClassLoader() .getResourceAsStream(“com/bjpowernode/test.properties”);
1.8、IO + Properties,怎么快速绑定属性资源文件?
//要求:第一这个文件必须在类路径下 //第二这个文件必须是以.properties结尾。 ResourceBundle bundle = ResourceBundle.getBundle(“com/bjpowernode/test”); String value = bundle.getString(key);
2、今日反射机制的重点内容 2.1、通过反射机制访问对象的某个属性。 2.2、通过反射机制调用对象的某个方法。 2.3、通过反射机制调用某个构造方法实例化对象。 2.4、通过反射机制获取父类以及父类型接口。
Java多线程基础 多线程 什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
对于java程序来说,当在DOS命令窗口中输入:java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
进程和线程是什么关系?举个例子
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东:进程
进程可以看做是现实生活当中的公司。
线程可以看做是公司当中的某个员工。
注意:
进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)
魔兽游戏是一个进程
酷狗音乐是一个进程
这两个进程是独立的,不共享资源。
线程A和线程B呢? 在java语言中:线程A和线程B,堆内存和方法区内存 共享。但是栈内存 独立,一个线程一个栈。 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
火车站,可以看做是一个进程。火车站中的每一个售票窗口可以看做是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。
对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
什么是真正的多线程并发?t1线程执行t1的。t2线程执行t2的。t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
单核的CPU表示只有一个大脑:不能 够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情同时在做! 线程A:播放音乐 线程B:运行魔兽游戏 线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
实现多线程的两种方式 第一种方式 实现线程的第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
怎么创建线程对象? new就行了。
怎么启动线程呢? 调用线程对象的start()方法。
注意:亘古不变的道理:方法体当中的代码永远都是自上而下的顺序依次逐行执行的。
以下程序的输出结果有这样的特点:有先有后。有多有少。这是咋回事?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class ThreadTest02 { public static void main (String[] args) { MyThread t = new MyThread (); t.start(); for (int i = 0 ; i < 100 ; i++){ System.out.println("主线程--->" + i); } } } class MyThread extends Thread { @Override public void run () { for (int i = 0 ; i < 100 ; i++){ System.out.println("分支线程--->" + i); } } }
第二种方式 实现线程的第二种方式,编写一个类实现java.lang.Runnable接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ThreadTest03 { public static void main (String[] args) { Thread t = new Thread (new MyRunnable ()); t.start(); for (int i = 0 ; i < 100 ; i++) System.out.println("主线程--->" + i); } } class MyRunnable implements Runnable { @Override public void run () { for (int i = 0 ; i < 100 ; i++){ System.out.println("分支线程--->" + i); } } }
匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ThreadTest04 { public static void main (String[] args) { Thread t = new Thread (new Runnable (){ @Override public void run () { for (int i = 0 ; i < 100 ; i++){ System.out.println("t线程---> " + i); } } }); t.start(); for (int i = 0 ; i < 100 ; i++){ System.out.println("main线程---> " + i); } } }
第三种方式 实现线程的第三种方式:实现Callable接口。(JDK8新特性。) 这种方式实现的线程可以获取线程的返回值。 之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
思考: 系统委派一个线程去执行一个任务,该线程执行完任务之后,可能 会有一个执行结果,我们怎么能拿到这个执行结果呢? 使用第三种方式:实现Callable接口方式。
实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ThreadTest15 { public static void main (String[] args) throws Exception { FutureTask task = new FutureTask (new Callable () { @Override public Object call () throws Exception { System.out.println("call method begin" ); Thread.sleep(1000 * 10 ); System.out.println("call method end!" ); int a = 100 ; int b = 200 ; return a + b; } }); Thread t = new Thread (task); t.start(); Object obj = task.get(); System.out.println("线程执行结果:" + obj); } }
怎么获取当前线程对象?
Thread t = Thread.currentThread();
返回值t就是当前线程,这个代码出现在main中,当前线程就是主线程
获取线程对象的名字
String name = 线程对象.getName();
修改线程对象的名字
当线程没有设置名字的时候,默认的名字有什么规律?main/Thread-0/Thread-1/Thread-2/Thread-3/…..
睡眠与终止 sleep 关于线程的sleep方法: static void sleep(long millis)
1、静态方法:Thread.sleep(1000); 2、参数是毫秒 3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。 4、Thread.sleep()方法,可以做到这种效果:间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ThreadTest06 { public static void main (String[] args) { for (int i = 0 ; i < 10 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?? 注意:这个不是终断线程的执行,是终止线程的睡眠。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ThreadTest08 { public static void main (String[] args) { Thread t = new Thread (new MyRunnable2 ()); t.setName("t" ); t.start(); try { Thread.sleep(1000 * 5 ); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }
终止 不建议使用:线程名称.stop(); 已过时,不建议使用,容易丢失数据!
合理方式:
打标记/改标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class ThreadTest10 { public static void main (String[] args) { MyRunable4 r = new MyRunable4 (); Thread t = new Thread (r); t.setName("t" ); t.start(); try { Thread.sleep(5000 ); } catch (InterruptedException e) { e.printStackTrace(); } r.run = false ; } } class MyRunable4 implements Runnable { boolean run = true ; @Override public void run () { for (int i = 0 ; i < 10 ; i++){ if (run){ System.out.println(Thread.currentThread().getName() + "--->" + i); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } }else { return ; } } } }
调度/优先级/让位/合并 常见的线程调度模型有哪些?
抢占式调度模型:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。
均分式调度模型:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。
优先级: 实例方法:
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
main线程的默认优先级是:5
优先级较高的,只是抢到的CPU时间片相对多一些。
大概率方向更偏向于优先级比较高的。
让位 静态方法:
static void yield() 让位方法
Thread.yield(); 让位,当前线程暂停,回到就绪状态,让给其它线程。
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。注意:在回到就绪之后,有可能还会再次抢到。
合并 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class ThreadTest13 { public static void main (String[] args) { System.out.println("main begin" ); Thread t = new Thread (new MyRunnable7 ()); t.setName("t" ); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("main over" ); } } class MyRunnable7 implements Runnable { @Override public void run () { for (int i = 0 ; i < 10000 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); } } }
synchronized 线程安全 为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是
你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点)
什么时候数据在多线程并发的环境下会存在安全问题呢?
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。
用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
怎么解决线程安全问题? 使用“线程同步机制”。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。 效率较低。线程排队执行。
同步就是排队。
实例变量:在堆中。 静态变量:在方法区。 局部变量:在栈中。
以上三大变量中: 局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。静态变量在方法区中,方法区只有1个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。 成员变量:可能会有线程安全问题。
如果使用局部变量的话:建议使用:StringBuilder。因为局部变量不存在线程安全问题。选择StringBuilder。StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。
银行取钱模拟 存在问题的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 public class Test { public static void main (String[] args) { Account act = new Account ("act-001" , 10000 ); Thread t1 = new AccountThread (act); Thread t2 = new AccountThread (act); t1.setName("t1" ); t2.setName("t2" ); t1.start(); t2.start(); } } class Account { private String actno; private double balance; public Account (String actno, double balance) { this .actno = actno; this .balance = balance; } public String getActno () { return actno; } public void setActno (String actno) { this .actno = actno; } public double getBalance () { return balance; } public void setBalance (double balance) { this .balance = balance; } public void withdraw (double money) { double before = this .getBalance(); double after = before - money; try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } this .setBalance(after); } } class AccountThread extends Thread { private Account act; public AccountThread (Account act) { this .act = act; } public void run () { double money = 5000 ; act.withdraw(money); System.out.println( Thread.currentThread().getName() + "账户" + act.getActno() + "取款" + money + "成功, 余额为" + act.getBalance()); } }
使用了线程同步机制解决问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public class Test { public static void main (String[] args) { Account act = new Account ("act-001" , 10000 ); Thread t1 = new AccountThread (act); Thread t2 = new AccountThread (act); t1.setName("t1" ); t2.setName("t2" ); t1.start(); t2.start(); } } class Account { private String actno; private double balance; public Account (String actno, double balance) { this .actno = actno; this .balance = balance; } public String getActno () { return actno; } public void setActno (String actno) { this .actno = actno; } public double getBalance () { return balance; } public void setBalance (double balance) { this .balance = balance; } public void withdraw (double money) { synchronized (this ) { double before = this .getBalance(); double after = before - money; try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } this .setBalance(after); } } } class AccountThread extends Thread { private Account act; public AccountThread (Account act) { this .act = act; } public void run () { double money = 5000 ; act.withdraw(money); System.out.println( Thread.currentThread().getName() + "账户" + act.getActno() + "取款" + money + "成功, 余额为" + act.getBalance()); } }
synchronized可以写在实例方法上,如果出现在实例方法上,一定锁的是this,不能有其他对象了,不灵活
另外还有一个缺点:它出现在实例方法上的时候表示整个方法体都要同步,可能会无故扩大同步的范围,导致程序的执行效率变低,所以这种方式不常用。
写在实例方法上有什么优点:代码下的少了,节俭了。
如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
同步代码块越小,效率越高
第一种:同步代码块 灵活 synchronized(线程共享对象){同步代码块;}
第二种:在实例方法上使用synchronized表示共享对象一定是this并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized表示找类锁。类锁永远只有1把。就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。类锁:100个对象,也可能只是1把类锁。
面试题 一 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Exam01 { public static void main (String[] args) throws InterruptedException { MyClass mc = new MyClass (); Thread t1 = new MyThread (mc); Thread t2 = new MyThread (mc); t1.setName("t1" ); t2.setName("t2" ); t1.start(); Thread.sleep(1000 ); t2.start(); } } class MyThread extends Thread { private MyClass mc; public MyThread (MyClass mc) { this .mc = mc; } public void run () { if (Thread.currentThread().getName().equals("t1" )){ mc.doSome(); } if (Thread.currentThread().getName().equals("t2" )){ mc.doOther(); } } } class MyClass { public synchronized void doSome () { System.out.println("doSome begin" ); try { Thread.sleep(1000 * 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over" ); } public void doOther () { System.out.println("doOther begin" ); System.out.println("doOther over" ); } }
不需要,因为doOther()方法没有synchronized
二 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Exam01 { public static void main (String[] args) throws InterruptedException { MyClass mc = new MyClass (); Thread t1 = new MyThread (mc); Thread t2 = new MyThread (mc); t1.setName("t1" ); t2.setName("t2" ); t1.start(); Thread.sleep(1000 ); t2.start(); } } class MyThread extends Thread { private MyClass mc; public MyThread (MyClass mc) { this .mc = mc; } public void run () { if (Thread.currentThread().getName().equals("t1" )){ mc.doSome(); } if (Thread.currentThread().getName().equals("t2" )){ mc.doOther(); } } } class MyClass { public synchronized void doSome () { System.out.println("doSome begin" ); try { Thread.sleep(1000 * 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over" ); } public synchronized void doOther () { System.out.println("doOther begin" ); System.out.println("doOther over" ); } }
需要,因为doOther()方法有synchronized了
三 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Exam01 { public static void main (String[] args) throws InterruptedException { MyClass mc1 = new MyClass (); MyClass mc2 = new MyClass (); Thread t1 = new MyThread (mc1); Thread t2 = new MyThread (mc2); t1.setName("t1" ); t2.setName("t2" ); t1.start(); Thread.sleep(1000 ); t2.start(); } } class MyThread extends Thread { private MyClass mc; public MyThread (MyClass mc) { this .mc = mc; } public void run () { if (Thread.currentThread().getName().equals("t1" )){ mc.doSome(); } if (Thread.currentThread().getName().equals("t2" )){ mc.doOther(); } } } class MyClass { public synchronized void doSome () { System.out.println("doSome begin" ); try { Thread.sleep(1000 * 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over" ); } public synchronized void doOther () { System.out.println("doOther begin" ); System.out.println("doOther over" ); } }
不需要,因为MyClass对象是两个,两把锁。
四 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Exam01 { public static void main (String[] args) throws InterruptedException { MyClass mc1 = new MyClass (); MyClass mc2 = new MyClass (); Thread t1 = new MyThread (mc1); Thread t2 = new MyThread (mc2); t1.setName("t1" ); t2.setName("t2" ); t1.start(); Thread.sleep(1000 ); t2.start(); } } class MyThread extends Thread { private MyClass mc; public MyThread (MyClass mc) { this .mc = mc; } public void run () { if (Thread.currentThread().getName().equals("t1" )){ mc.doSome(); } if (Thread.currentThread().getName().equals("t2" )){ mc.doOther(); } } } class MyClass { public synchronized static void doSome () { System.out.println("doSome begin" ); try { Thread.sleep(1000 * 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over" ); } public synchronized static void doOther () { System.out.println("doOther begin" ); System.out.println("doOther over" ); } }
synchronized出现在静态方法上是找类锁。
需要,因为静态方法是类锁,不管创建了几个对象,类锁只有1把。
死锁 死锁代码要会写。 一般面试官要求你会写。 只有会写的,才会在以后的开发中注意这个事儿。 因为死锁很难调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class DeadLock { public static void main (String[] args) { Object o1 = new Object (); Object o2 = new Object (); Thread t1 = new MyThread1 (o1,o2); Thread t2 = new MyThread2 (o1,o2); t1.start(); t2.start(); } } class MyThread1 extends Thread { Object o1; Object o2; public MyThread1 (Object o1,Object o2) { this .o1 = o1; this .o2 = o2; } public void run () { synchronized (o1){ try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ } } } } class MyThread2 extends Thread { Object o1; Object o2; public MyThread2 (Object o1,Object o2) { this .o1 = o1; this .o2 = o2; } public void run () { synchronized (o2){ try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ } } } }
synchronized 在开发过程中最好不要嵌套使用
怎么解决线程安全问题 是一上来就选择线程同步吗?synchronized 不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
守护线程/定时器 守护线程 java语言中线程分为两大类: 一类是:用户线程 一类是:守护线程(后台线程) 其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点: 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。
守护线程用在什么地方呢? 每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
设置为守护线程 t.setDaemon(true);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ThreadTest14 { public static void main (String[] args) { Thread t = new BakDataThread (); t.setName("备份数据的线程" ); t.setDaemon(true ); t.start(); for (int i = 0 ; i < 10 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } class BakDataThread extends Thread { public void run () { int i = 0 ; while (true ){ System.out.println(Thread.currentThread().getName() + "--->" + (++i)); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }
定时器 定时器的作用:间隔特定的时间,执行特定的程序。
每周要进行银行账户的总账操作。每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class TimerTest { public static void main (String[] args) throws Exception { Timer timer = new Timer (); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); Date firstTime = sdf.parse("2020-03-14 09:34:30" ); timer.schedule(new TimerTask (){ @Override public void run () { } } , firstTime, 1000 * 10 ); } } class LogTimerTask extends TimerTask { @Override public void run () { SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); String strTime = sdf.format(new Date ()); System.out.println(strTime + ":成功完成了一次数据备份!" ); } }
生产者和消费者模式 关于Object类中的wait和notify方法
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。wait方法和notify方法不是通过线程对象调用,不是这样的:t.wait(),也不是这样的:t.notify()..不对。
第二:wait()方法作用?Object o = new Object();o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。
第三:notify()方法作用?Object o = new Object(); o.notify();
表示:唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程。
实现 使用wait方法和notify方法实现“生产者和消费者模式”
什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。
生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
模拟这样一个需求:
仓库我们采用List集合。List集合中假设只能存储1个元素。1个元素就表示仓库满了。如果List集合中元素个数是0,就表示仓库空了。保证List集合中永远都是最多存储1个元素。必须做到这种效果:生产1个消费1个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 public class ThreadTest16 { public static void main (String[] args) { List list = new ArrayList (); Thread t1 = new Thread (new Producer (list)); Thread t2 = new Thread (new Consumer (list)); t1.setName("生产者线程" ); t2.setName("消费者线程" ); t1.start(); t2.start(); } } class Producer implements Runnable { private List list; public Producer (List list) { this .list = list; } @Override public void run () { while (true ){ synchronized (list){ if (list.size() > 0 ){ try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Object obj = new Object (); list.add(obj); System.out.println(Thread.currentThread().getName() + "--->" + obj); list.notifyAll(); } } } } class Consumer implements Runnable { private List list; public Consumer (List list) { this .list = list; } @Override public void run () { while (true ){ synchronized (list) { if (list.size() == 0 ){ try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Object obj = list.remove(0 ); System.out.println(Thread.currentThread().getName() + "--->" + obj); list.notifyAll(); } } } }
面试题 sleep 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class ThreadTest07 { public static void main (String[] args) { Thread t = new MyThread3 (); t.setName("t" ); t.start(); try { t.sleep(1000 * 5 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello World!" ); } } class MyThread3 extends Thread { public void run () { for (int i = 0 ; i < 10000 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); } } }
答案:
在执行的时候还是会转换成:Thread.sleep(1000 * 5);
这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
这样代码出现在main方法中,main线程睡眠。
JDBC 介绍 JDBC是什么?
Java Database Counnectivity(Java语言链接数据库)
本质是什么?
SUN公司制定的一套接口(interface)
接口都有调用者/实现者
面向接口调用/面向接口写实现类,都属于面向接口编程
为什么面向接口编程?
解耦合,降低程序的耦合度,提高程序的扩展力
多态就是非常典型的面向抽象编程(不要面向具体过程编程)
为什么制定这一套接口呢?
因为每一个数据可以的底层实现原理都不一样(oracle/mysql/ms sqlserver)
每一个数据库产品都有自己独特的实现原理。
开发的准备:如果是用文本编辑器开发,则需要配置classpath文件,用IDEA的时候,不需要
API 主要成员变量 JDBC API 主要功能:三件事,具体是通过以下类/接口实现:
DriverManager : 管理jdbc驱动
Connection: 连接(通过DriverManager产生)
Statement(PreparedStatement) :增删改查 (通过Connection产生 )
CallableStatement : 调用数据库中的 存储过程/存储函数 (通过Connection产生 )
Result :返回的结果集 (上面的Statement等产生 )
Connection产生操作数据库的对象:
Connection产生Statement对象:createStatement()
Connection产生PreparedStatement对象:prepareStatement()
Connection产生CallableStatement对象:prepareCall();
Statement操作数据库:
PreparedStatement操作数据库:
因为 public interface PreparedStatement extends Statement
所以
增删改:executeUpdate()
查询:executeQuery();
赋值操作 setXxx();
PreparedStatement与Statement在使用时的区别:
Statement: sql executeUpdate(sql)
PreparedStatement: sql(可能存在占位符?) 在创建PreparedStatement 对象时,将sql预编译 prepareStatement(sql) executeUpdate() setXxx()替换占位符?
推荐使用PreparedStatement:原因如下:
1.编码更加简便(避免了字符串的拼接)
2.提高性能(因为 有预编译操作,预编译只需要执行一次)
3.安全(可以有效防止sql注入)
JDBC编程六步
注册驱动(作用:告诉Java程序,即将链接的是哪个品牌的数据库)
获取链接(表示JVM的进程和数据库进程之间的通道打开 了,属于进程之间的通信,重量级的,使用完一定要关闭)
获取数据库操作对象(专门执行sql语句的对象)
执行SQL语句(DQL and DML)
处理查询结果集(只有当4执行的是select语句的时候,才有这个处理查询结果集)
释放资源(使用完资源之后一定要关闭资源)
普通数据增删改查实例 练习1 向dept中添加一个职位‘人事部’,no为50,地址为北京
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import java.sql.*;public class Test { public static void main (String[] args) { Statement stmt = null ; Connection conn = null ; try { Class.forName("com.mysql.jdbc.Driver" ); String url = "jdbc:mysql://127.0.0.1:3306/mytest" ; String user = "root" ; String password = "333" ; conn = DriverManager.getConnection(url, user, password); stmt = conn.createStatement(); String sql = "insert into dept(deptno, dname, loc) value(60, 'renshi2', 'beijing1')" ; int count = stmt.executeUpdate(sql); System.out.print(count == 1 ? "success" : "fail" ); }catch (SQLException e){ e.printStackTrace(); }finally { } } }
练习2 删除一条数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import java.sql.*;import java.util.*;public class Test { public static void main (String[] args) { Connection conn = null ; Statement stmt = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); stmt = conn.createStatement(); String sql = "delete from dept where deptno = 50" ; int count = stmt.executeUpdate(sql); if (count == 1 ) System.out.print("yes" ); }catch (ClassNotFoundException e){ e.printStackTrace(); }catch (SQLException e){ e.printStackTrace(); }finally { } } }
将链接数据库的所有信息配置到信息文件中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import java.sql.*;import java.util.*;public class Test { public static void main (String[] args) { ResourceBundle bundle = ResourceBundle.getBundle("jdbc" ); String url = bundle.getString("url" ); String user = bundle.getString("user" ); String password = bundle.getString("password" ); Connection conn = null ; Statement stmt = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection(url, user, password); stmt = conn.createStatement(); String sql = "insert into dept(deptno, dname, loc) value(50,'HumanResource','Beijing')" ; int count = stmt.executeUpdate(sql); if (count == 1 ) System.out.print("yes" ); }catch (ClassNotFoundException e){ e.printStackTrace(); }catch (SQLException e){ e.printStackTrace(); }finally { } } }
查询结果集的处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import java.sql.*;public class Test { public static void main (String[] args) { Connection conn = null ; Statement stmt = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); stmt = conn.createStatement(); String sql = "select ename name ,sal s from emp" ; rs = stmt.executeQuery(sql); while (rs.next()){ String ename = rs.getString("name" ); Double sal = rs.getDouble("s" ); System.out.println(ename + " " + sal); } }catch (Exception e){ e.printStackTrace(); }finally { } } }
用户登录功能实现 可以对sql脚本进行编辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 drop table if exists t_user;create table t_user( id bigint auto_increment, loginName varchar (255 ), loginPwd varchar (255 ), realName varchar (255 ), primary key (id) ); INSERT INTO t_user( loginName, loginPwd, realName ) VALUES ( 'zhangsan' , '123' , '张三' ), ( 'jack' ,'123' ,'杰克' ), ( 'lisi' ,'123' ,'李四' );
问题代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 import java.sql.*;import java.util.HashMap;import java.util.Map;import java.util.Scanner;public class Test { public static void main (String[] args) { Map<String,String> userLoginInfo = initUI(); boolean loginRst = login(userLoginInfo); System.out.println(loginRst ? "登录成功" : "登录失败" ); } private static boolean login (Map<String, String> userLoginInfo) { boolean loginSuccess = false ; Connection conn = null ; Statement stmt = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); stmt = conn.createStatement(); String sql = "select * from t_user where loginName = '" + userLoginInfo.get("loginName" ) + "' and loginPwd = '" + userLoginInfo.get("loginPwd" ) + "'" ; rs = stmt.executeQuery(sql); if (rs.next()){ loginSuccess = true ; } } catch (Exception e) { e.printStackTrace(); }finally { try { if (rs != null ) { rs.close(); } } catch (SQLException s) { s.printStackTrace(); } try { if (stmt != null ){ stmt.close(); } }catch (SQLException s){ s.printStackTrace(); } try { if (conn != null ){ conn.close(); } }catch (SQLException s){ s.printStackTrace(); } } return loginSuccess; } private static Map<String, String> initUI () { Scanner s = new Scanner (System.in); System.out.println("用户名:" ); String username = s.next(); System.out.println("密码:" ); String password = s.next(); Map<String, String> userLoginInfo = new HashMap <>(); userLoginInfo.put("loginName" , username); userLoginInfo.put("loginPwd" , password); return userLoginInfo; } }
SQL注入 用户名:asd
密码:asd’or’1’ = ‘1
原因:用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql的原意被扭曲。
解决:
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用,要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement. 这个接口继承了java.sql.Statement,是预编译的数据库操作对象,原理:预先对SQL语句的框架进行编译,然后再给SQL语句传值。
对比Statement和PerparedStatement:
Statement存在SQL注入问题,preparedstatement解决了SQL注入问题。
Statement是编译一次执行一次。preparedstatement是编译一次,可执行多次。
PreparedStatement效率较高一些。
PreparedStatement会在编译阶段做类型的安全检查。
综上所述:Preparedstatement使用较多。只有极少数的情况下需要使用statement
什么情况下必须使用statement呢?
业务方面要求必须支持SQL注入的时候。需要进行SQL语句的拼接的时候。
Statement支持SQL注入,凡是业务方面要求是需要进行SQL语句拼接的,必须使用Statement
最终版本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 import java.sql.*;import java.util.HashMap;import java.util.Map;import java.util.Scanner;public class Test { public static void main (String[] args) { Map<String,String> userLoginInfo = initUI(); boolean loginRst = login(userLoginInfo); System.out.println(loginRst ? "登录成功" : "登录失败" ); } private static boolean login (Map<String, String> userLoginInfo) { boolean loginSuccess = false ; Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); String sql = "select * from t_user where loginName = ? and loginPwd = ?" ; ps = conn.prepareStatement(sql); ps.setString(1 , userLoginInfo.get("loginName" )); ps.setString(2 , userLoginInfo.get("loginPwd" )); rs = ps.executeQuery(); if (rs.next()){ loginSuccess = true ; } } catch (Exception e) { e.printStackTrace(); }finally { try { if (rs != null ) { rs.close(); } } catch (SQLException s) { s.printStackTrace(); } try { if (ps != null ){ ps.close(); } }catch (SQLException s){ s.printStackTrace(); } try { if (conn != null ){ conn.close(); } }catch (SQLException s){ s.printStackTrace(); } } return loginSuccess; } private static Map<String, String> initUI () { Scanner s = new Scanner (System.in); System.out.println("用户名:" ); String username = s.next(); System.out.println("密码:" ); String password = s.next(); Map<String, String> userLoginInfo = new HashMap <>(); userLoginInfo.put("loginName" , username); userLoginInfo.put("loginPwd" , password); return userLoginInfo; } }
PrepareStatement 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import java.sql.*;public class Test { public static void main (String[] args) { Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); String sql = "insert into dept(dname, loc, deptno) values(?, ?, ?)" ; ps = conn.prepareStatement(sql); ps.setString(1 , "HR" ); ps.setString(2 , "Shanghai" ); ps.setInt(3 , 70 ); int count = ps.executeUpdate(); System.out.println(count); } catch (Exception e) { e.printStackTrace(); }finally { if (rs != null ) { try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (ps != null ) { try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null ) { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
银行转账业务 JDBC事务默认自动提交,
关闭自动提交? conn.setAutoCommit(false);
提交事务 conn.commit();
回滚事务 conn.rollback();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import java.sql.*;public class Test { public static void main (String[] args) { Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); conn.setAutoCommit(false ); String sql = "update t_account set balance = ? where act = ?" ; ps = conn.prepareStatement(sql); ps.setDouble(1 , 10000 ); ps.setString(2 , "111" ); int count = ps.executeUpdate(); ps.setDouble(1 , 10000 ); ps.setString(2 , "222" ); count += ps.executeUpdate(); System.out.println(count == 2 ? "转账成功" : "转账失败" ); conn.commit(); } catch (Exception e) { if (conn != null ){ try { conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } e.printStackTrace(); }finally { if (rs != null ) { try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (ps != null ) { try { ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null ) { try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
JDBC工具类的封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import java.sql.*;public class DBUtil { private DBUtil () {} static { try { Class.forName("com.mysql.jdbc.Driver" ); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection () throws SQLException{ return DriverManager.getConnection("jdbc:mysql://localhost:3306/mytest" , "root" , "333" ); } public static void close (Connection conn, Statement ps, ResultSet rs) { if (rs != null ){ try { rs.close(); } catch (SQLException s) { s.printStackTrace(); } } if (ps != null ){ try { ps.close(); } catch (SQLException s) { s.printStackTrace(); } } if (conn != null ){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
基于工具类的模糊查询 查询员工中名字有A的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.sql.*;public class Test { public static void main (String[] args) { Connection conn = null ; PreparedStatement ps = null ; ResultSet rs = null ; try { Class.forName("com.mysql.jdbc.Driver" ); conn = DBUtil.getConnection(); String sql = "select ename from emp where ename like ?" ; ps = conn.prepareStatement(sql); ps.setString(1 , "_A%" ); rs = ps.executeQuery(); while (rs.next()){ System.out.println(rs.getString("ename" )); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtil.close(conn, ps, rs); } } }
存储过程与存储函数调用 CallableStatement:调用 存储过程、存储函数
connection.prepareCall(参数:存储过程或存储函数名)
参数格式:
存储过程(无返回值return,用out参数替代):
{ call 存储过程名(参数列表) }
存储函数(有返回值return):
{ ? = call 存储函数名(参数列表) }
1 2 3 4 create or replace procedure addTwoNum ( num1 in number,num2 in number,result out number ) as begin result := num1+num2 ; end ;
强调:
如果通过sqlplus 访问数据库,只需要开启:OracleServiceSID
通过其他程序访问数据(sqldevelop、navicate、JDBC),需要开启:OracleServiceSID、XxxListener
JDBC调用存储过程的步骤:
a. 产生 调用存储过程的对象(CallableStatement) cstmt = connection.prepareCall( “…” ) ;
b. 通过setXxx()处理 输出参数值 cstmt.setInt(1, 30);
c. 通过 registerOutParameter(…)处理输出参数类型
d.cstmt.execute()执行
e.接受 输出值(返回值)getXxx()
调存储函数:
1 2 3 4 5 6 7 8 create or replace function addTwoNumfunction ( num1 in number,num2 in number) -- 1 + 2 return number as result number ; begin result := num1+num2 ; return result ; end ;
JDBC调用存储函数:与调存储过程的区别: 在调用时,注意参数:”{? = call addTwoNumfunction (?,?) }”
存储过程 首先在数据库中创建存过程:
1 2 3 4 CREATE OR REPLACE PROCEDURE ADDNUM(num1 in number, num2 in number, rst out number) AS BEGIN rst := num1 + num2; END ADDNUM;
然后进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Connection conn = null ;CallableStatement callableStatement = null ;try { conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORCL" , "c##libmanager" , "333" ); callableStatement = conn.prepareCall("{call addNum(?, ?, ?)}" ); callableStatement.setInt(1 , 13 ); callableStatement.setInt(2 , 10 ); callableStatement.registerOutParameter(3 , Types.INTEGER); callableStatement.execute(); int rst = callableStatement.getInt(3 ); System.out.println(rst); } catch (SQLException throwables) { throwables.printStackTrace(); }
存储函数 在数据库中创建函数:
1 2 3 4 5 6 7 8 CREATE OR REPLACE FUNCTION addNum ( NUM1 IN NUMBER , NUM2 IN NUMBER ) RETURN NUMBER AS BEGIN RETURN num1 + num2; END addNum;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Connection conn = null ;CallableStatement callableStatement = null ;try { conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORCL" , "c##libmanager" , "333" ); callableStatement = conn.prepareCall("{? = call addNum(?, ?)}" ); callableStatement.setInt(2 , 13 ); callableStatement.setInt(3 , 10 ); callableStatement.registerOutParameter(1 , Types.INTEGER); callableStatement.execute(); int rst = callableStatement.getInt(1 ); System.out.println(rst); } catch (SQLException throwables) { throwables.printStackTrace(); }
大类型数据存储与读取实例 处理CLOB/BLOB类型 - 处理稍大型数据:
a.存储路径 E:\JDK_API_zh_CN.CHM 通过JDBC存储文件路径,然后 根据IO操作处理
例如:JDBC将 E:\JDK_API_zh_CN.CHM 文件 以字符串形式 “E:\JDK_API_zh_CN.CHM” 存储到数据库中
获取:1.获取该路径“E:\JDK_API_zh_CN.CHM” 2.IO
b.
CLOB:大文本数据 (小说->数据)
BLOB:二进制
clob:大文本数据 字符流 Reader Writer
存:
先通过pstmt 的? 代替小说内容 (占位符)
再通过pstmt.setCharacterStream(2, reader, (int)file.length()); 将上一步的?替换为 小说流, 注意第三个参数需要是 Int类型
取:
通过Reader reader = rs.getCharacterStream(“NOVEL”) ; 将cloc类型的数据 保存到Reader对象中
将Reader通过Writer输出即可。
blob:二进制 字节流 InputStream OutputStream
与CLOB步骤基本一致,区别:setBinaryStream(…) getBinaryStream(…)
大文本存储 存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 String sql = "insert into mynovel values(?,?)" ;pstmt = connection.prepareStatement(sql); pstmt.setInt(1 , 1 ); File file = new File ("E:\\all.txt" );InputStream in = new FileInputStream ( file) ;Reader reader = new InputStreamReader ( in ,"UTF-8" ) ;pstmt.setCharacterStream(2 , reader, (int )file.length()); int count = pstmt.executeUpdate() ;if (count > 0 ) { System.out.println("操作成功!" ); } reader.close();
读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String sql = "select NOVEL from mynovel where id = ? " ;pstmt = connection.prepareStatement(sql); pstmt.setInt(1 , 1 ); rs = pstmt.executeQuery() ; if (rs.next()){ Reader reader = rs.getCharacterStream("NOVEL" ) ; Writer writer = new FileWriter ("src/小说.txt" ); char [] chs = new char [100 ] ; int len = -1 ; while ( (len = reader.read(chs)) !=-1 ) { writer.write( chs,0 ,len ); } writer.close(); reader.close(); }
音乐存储 存储
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String sql = "insert into mymusic values(?,?)" ;pstmt = connection.prepareStatement(sql); pstmt.setInt(1 , 1 ); File file = new File ("d:\\luna.mp3" );InputStream in = new FileInputStream (file );pstmt.setBinaryStream(2 ,in ,(int )file.length() ); int count = pstmt.executeUpdate() ;if (count > 0 ) { System.out.println("操作成功!" ); } in.close();
读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String sql = "select music from mymusic where id = ? " ;pstmt = connection.prepareStatement(sql); pstmt.setInt(1 , 1 ); rs = pstmt.executeQuery() ; if (rs.next()){ InputStream in = rs.getBinaryStream("music" ) ; OutputStream out = new FileOutputStream ("src/music.mp3" ) ; byte [] chs = new byte [100 ] ; int len = -1 ; while ( (len = in.read(chs)) !=-1 ) { out.write( chs,0 ,len ); } out.close(); in.close(); }