这里写目录标题
- 一级目录
- 数组
- 字符串
- 控制台输入
- 方法
- 方法重载
- 面向对象&过程
- 类和对象
- 集成开发环境--简称IDE
- 面向对象的封装
- 构造方法
- 区分几个变量
- this关键字
- this关键字什么时候不能省略
- 通过this调用其他构造方法
- static关键字
- static来定义“静态代码块”
- 总结
- 继承
- 方法覆盖override
- 多态
- 多态在实际开发中的作用
- final 关键字
- package和import
- 关于访问控制权限
- super关键字
- IDEA
- 抽象类abstract
- 接口
- object--老祖宗
- tostring方法
- equals方法
- finalize()
- hashcode()
- 匿名内部类
- 数组array
- main方法中的String[] args
- 一维数组扩容
- 二维数组
- Arrays
- String
- String中的构造方法
- String中的常用方法
- StringBuffer和StringBuilder
- 八种包装类型
- 自动装箱&&自动拆箱
- Integer类中常用的方法
- int&Integer&String之间的转化
- Java中对日期的处理
- 总结System中的方法
- 数字的格式化(了解即可)
- BigDecimal
- 随机数
- 枚举类型
- 异常
- 异常对象的两个重要方法
- try、catch中的finally子句
- 自定义异常
- 集合Collection(单键) + Map(键对)
- Colletion中常用方法
- Iterator--Collection及子类可使用
- List中特有方法
- ArrayList
- Vector
- Map<K,V>中常用方法
- 遍历MAP
- HashMap
- Hashtable & Properties
- TreeMap
- 集合工具类
- UML图画法
- 泛型
- 增强for循环-foreach
- IO流
- java.io.File类
- 拷贝目录
- IO和properties联合使用
- 线程
- 反射机制
一级目录
数组
int [ ] arr = new int [ 5 ]; // 默认值为0,长度为5的数组.
int [ ] arr = new int [ ]{ 1,2,3,4,5 } //声明时直接赋值 长度为5 值为1,2,3,4,5的数组.
int [ ] arr = { 1,2,3,4,5 } //同上
- 利用for循环遍历数组
for( //数组的数据类型 变量名字 : 要遍历的数组){
System.out.print(变量名字);
}
public class Array {
public static void main(String[] args) {
int [] arr = new int[5];
for(int i=0;i<arr.length;i++) {
arr[i] = i;
}
for(int num:arr) {
System.out.print(num+"\t");
}
}
}
- 调用Arrays类的toString方法
import java.util.Arrays;
public class Array {
public static void main(String[] args) {
int[] arr = new int[5];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
System.out.println(Arrays.toString(arr));
}
}
//控制台输出结果:0 1 2 3 4
字符串
String为引用数据类型
如图所示,num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容。
控制台输入
//第一步:创建键盘扫描器对象
java.util.Sanner s = new java.util.Scanner(System.in);
//第二步:调用Scanner对象的next()方法接受用户键盘输入
String userInput = s.next();//以字符串的形式接收
int num = s.nextInt();//以整数型int来接收
方法
方法体当中不能再定义方法
- void不能省略
- 当返回值是void时,最后不能写 “return 值;”这样的语句,但是可以写 “return;”
- 方法名首字母要求小写,后面每个单词首字母要大写
- 对于public static这样定义的方法,调用时采用 “." 如上面的MethodTest02.sumInt,表示调用某个类的某个方法
- 建议在一个java文件中只定义一个class,不要学图二的方法
- 方法只有在被调用的时候才会分配内存,在JVM内存划分上有这样主要的三个空间(还有其他空间):方法区内存;堆内存;栈内存;
方法重载
注意:
前提是功能相似;通过传入参数的个数、顺序及类型不同来判断调用哪个函数;函数必须在同一类中
可以将sum的三条语句封装到另外一个文件的一个类中
面向对象&过程
面向对象的三大特征:封装;继承;多态
类和对象
定义类的时候第一个字母要大写,有点像C语言中的结构体
集成开发环境–简称IDE
面向对象的封装
public class User
{
private int age;
public void setAge(int a )
{
age = a;
}
public int getAge()//set和get方法定义的时候不要加static
{
return age;
}
}
//如何调用
public class UserTest
{
public static void main(String args[])
{
User a = new User();
//修改
a.setAge(30);
//读取
System.out.println(user.getAge());
}
}
构造方法
doOther这个函数没有static修饰 必须用 ”引用.函数名“ 的方式调用,所以要先创建对象t , 在这里由于类中没有定义构造方法,自动生成一个没有参数的构造方法
doSome也可以用这种方式,但前面有static ,可以直接用doSome()或ConstructorTest01.doSome()的方式调用
创建一个User对象,就是使用User类的构造方法来完成的,使用new + 构造方法名(),而构造方法名和类名是一致的
User u = new User();//这里User不能理解为一个类名,而是一个(构造)方法--加了括号的都是方法
构造方法结束后返回值是构造方法所在类的类型--返回值类型就是类本身, 所以不需要写返回值
== new对象就是用无参数构造器(public + 类名的函数)==
构造方法的作用:
1.创建对象
2.创建对象的同时,初始化实例变量的内存空间。
成员变量之实例变量,属于对象级别的变量,这种变量必须先有对象才能有实例变量。
实例变量没有手动赋值的时候,系统默认赋值,那么这个系统默认赋值是在什么时候完成的呢?实在类加载的时候嘛?
NO!实际上,实例变量的内存空间是在构造方法执行过程当中完成开辟的,完成初始化的。
实例变量默认值:
byte ,short ,int ,long -- 0
float , double -- 0..0
boolean -- false
引用数据类型 -- null
public class Account()
{
private String actno;//账号
private double balance;//余额
//无参数构造器
public Account()
{
//actno = null;
//balance = 0.0;
}
public Account(String s)
{
actno = s;
//balance = 0.0;
}
public String getActno()
{
return actno;
}
}
//
public class ConstructorTest{
public static void main(String[] args){
Account act1 = new Account();
System.out.println(act1.getActno());
System.out.println(act1.getBalance());//输出null 0.0
Account act2 = new Account(“110”);
System.out.println(act2.getActno());
System.out.println(act2.getBalance());//输出110 0.0
区分几个变量
1.局部变量是方法中的变量。(形参也属于局部变量)
2 .成员变量是独立于方法之外的变量。
而 java类的成员变量又有两种:
1.静态变量(类变量): 独立于方法之外的变量,用 static 修饰。
2.实例变量: 独立于方法之外的变量,不过没有 static 修饰。
this关键字
当使用c1去访问该对象的话,整个过程中出现的this就是c1
其实应该写
System.out.println( c1. name + " 在购物!“);
System.out.println( c2. name + " 在购物!”);
直接用this可以代表c1、c2或其他不同的对象
System.out.println( this. name + " 在购物!");
多数情况下 this也可以省略不写,直接写System.out.println(name + " 在购物!");
回顾之前封装的时候,每个变量都要有一个get函数和一个set函数,当时让死记硬背“ 都不能加static 关键字 ” 就是因为修改和得到的值都要针对不同对象来说。this不能用在带static的方法中!!!
最终结论:在带有static的方法中不能直接访问实例变量和实例方法(即不带static的变量和不带static的方法)
因为实例变量和实例方法都需要对象的存在
而static的方法当中是没有this的,也就是说当前对象是不存在的
自然无法范根当前对象的实例变量和实例方法
this关键字什么时候不能省略
//用户类
public class User{
//属性
private int id;
private String naem;
//这种写法是可以的
public void setId(int a){
id = a;//就相当于 this id = a;
}
//为了让传入的参数见名知意,想要采用下种写法
//但是这种中国写法不行,因为java有就近原则
public void setId(int id){
id = id;
}
//上面的写法采用下面的方式改正
public void setId(int id){
this.id = id; //由此可见,this用来区分实例变量和局部变量的时候不能省略
}
通过this调用其他构造方法
//目前有一个日期类,包含年、月、日,还有两个构造器,一个三个参数全有,一个无参数
//规定:当使用无参数构造器时,默认创建日期为1970-1-1
public Data(int year, int month , int day){
this.year = year;
this.month = month;
this.day = day;
}
public Data(){
/*this.year = 1970;
this.month = 1;
this.day = 1;*/
//以上代码可用下面的代码代替
this(1970, 1, 1);//调用了有参数的构造器
//注意,上面这行代码只能放在第一行,这也代表着在一个构造器里只能调用一次其他的构造方法
public class Test{
int i = 10;
public static void dosome(){
System.out.println("do some!");
}
public void doOther(){
System.out.println("do other!");
}
public static void method1(){
//调用dosome
Test.dosome();
//省略方式
dosome();
//调用doOther
Test t = new Test();
t.doOther;
//访问i
System.out.println(t.i);
//错误写法
System.out.println(i);
}
public void method2(){
//调用dosome
Test.dosome();
//省略方式
dosome();
//调用doOther
this.doOther;//********************************
//省略方式调用
doOther();
//访问i
System.out.println(this.i);
//省略写法
System.out.println(i);
}
//主方法
public static void main(String args[]){
//调用method1
Test.method1();
//省略写法
method1();
//调用method2
Test t = new Test();
t.method2();
}
static关键字
public class Chinese
{
//实例变量是针对每一个对象的,不同对象的实例变量值不同
String id;
String name;
//静态变量,所有对象国籍是一样的,这种特征属于“类级别”的特征,可以提升为整个模板的特征
static String country = “中国”;
//静态变量再类加载的时候初始化,不需要创建对象内存就开辟了
//静态变量存=储存在方法区内存中,这样能节省很多内存空间
}
在访问的时候,直接为Chinese.country
所有静态的数据都可以采用“类名.”或“引用.”的方式,但是不要采用第二种方式!!!
当采用“引用.”的方式访问的时候,即使引用是null,也不会出现空指针异常 ,因为访问静态的数据不需要对象的存在
所有有static修饰的变量或方法,统一用"类名."的方式调用!!
static来定义“静态代码块”
1.语法格式:
static {
Java语句
}
2.静态代码块在类加载时执行,并且只执行一次
3.静态代码块在一个类中可以写多个,并且遵循自上而下的顺序依次执行
4.静态代码块的作用和实际项目有关系, 例如项目中要求在类加载时刻执行代码完成日志的记录,那么这段记录日志的代码就可以编写到静态代码块中。静态代码块时Java为程序员准备的一个特殊的时刻,这个时刻被称为类加载时刻。若希望在这个时刻执行一段特殊的程序,这段程序可以直接放到静态代码块中。
总结
class 类{
静态代码块//类加载时执行,并且只执行一次
静态变量//放方法区内存中,类加载时初始化
实例变量//放堆内存中,构造方法执行的时候初始化
构造方法//制造对象的,new
静态方法
实例方法
}
继承
1.继承是面向对象三大特征之一
2.继承的基本作用是:代码复用。但是继承最重要的作用是:有了继承才有了以后方法的覆盖和多态机制。
3.继承的语法格式:
【修饰符列表】class 类名 extends 父类名{
类体 = 属性 + 方法;
}
4.Java语言当中的继承只支持单继承,一个类中不能同时继承很多类。在c++中支持多继承。
5.关于继承中的一些术语:
B类继承A类
A类称为–父类、基类、超类、superclass
B类称为–子类、派生类、subclass
6.在Java中,子类继承父类:
私有的不支持继承;
构造方法不支持继承;
其他数据都可以被继承;
7.虽然Java只支持单继承,但一个类也可以间接继承其他类,例如:
C extendsB{
}
B extends A{
}
8.Java语言中假设一个类没有显示继承任何类,该类默认继承JavaSE库当中提供的java . lang . Object类
方法覆盖override
回顾方法重载:
1.方法重载又称为overload
2.方法重载什么时候使用?
当在同一个类中,方法完成的功能是相似的,建议方法名相同,这样方便程序员的编程,就像在调用一个方法
3.什么条件满足之后构成方法重载?
*在同一个类中
*方法名相同
*参数列表不同:类型、顺序、个数
4.方法重载和什么无关?
*和方法的返回值无关
*和方法的修饰符列表无关
多态
强制类型转化(父类转化为子类)的目的:调用子类中特殊的方法
在进行强制类型转换之前,建议采用instanceof运算符进行判断,避免出现错误
Animal a1 = new Cat();
Animal a2 = new Bird();
if (a1 instanceof Cat){
Cat c1 = (Cat) a1;
}
if (a2 instanceof Bird){
Bird b2 = (Bird) a2;
}
多态在实际开发中的作用
见视频154
太奇妙了!我的妈呀!
final 关键字
报错:无法为最终变量分配值
1.final表示最终的、最后的、不可变的,可以修饰变量、方法还有类
2.final修饰类:如果不希望对X类进行扩展,可以给X类加final关键字,则无法继承X,也就无法对X中的方法重写了(对X绝育)
3.final修饰方法:子类中无法对该方法进行重写
4.final修饰变量:一旦赋值,不能重新赋值
5.final修饰的变量如果是一个引用:指向的地址就不能再改变了,并且该对象不会被垃圾回收器回收,直到当前方法结束才会释放空间。但是引用对象内部的值可以改变
6.final修饰的变量是实例变量:系统不会赋默认值,必须自己手动赋值。注意实例变量在构造方法里赋值,所以手动赋值要赶在系统赋默认值之前。为了节省内存,final修饰实例变量时往往与static一起使用,这样空间在方法区中开辟一次。(static final联合修饰的变量称为常量,常量名建议全部大写,每个单词之间采用下划线连接 )
7.常量和静态变量都是存储在方法区,并且都是在类加载时初始化。区别是常量不能改变。都采用类门.变量名的方法访问。常量一般都是公开的,可以不用封装,因为根本改不了。
package和import
package是一个关键字,包机制的作用是为了方便程序的管理。后面加包名,例如:package com.bjpowernode.javase.chapter17,注意只能出现在代码的第一行
包名的命名规范:公司域名倒叙 + 项目名 + 模块名 + 功能名
import什么时候用?
A类中使用B类。
A类和B类都在同一个包下,不需要import。
A类和B类不在同一个包下,需要import。
import语句只能出现在package语句之下,class声明语句之上。
import还可以使用*语句。
lang包下的直接子包不需要导入。
关于访问控制权限
访问控制权限修饰符:
1.访问控制权限修饰符来控制元素的访问范围
2.访问控制权限修饰符包括:
public 表示公开的,在任何位置都可以访问
private 表示私有的,只能在本类中访问
protected 同包,子类可以访问
缺省 即什么都不加,如int i = 10;只有同包能访问
3、访问控制权限修饰符可以修饰类、变量、方法
4.当某个数据只希望子类使用,使用protected进行修饰。
5.修饰符的范围:
private < 缺省 < protected < public
6.类只能用public和缺省(default)修饰,内部类除外
super关键字
1.super和this对比着学
this:
能出现在实例方法中和构造方法中。
this的语法是:“ this. ”、“ this()”。
this不能使用在静态方法中。
super:
super能出现在实例方法中和构造方法中。
super的语法是:“ super. ”、“ super()”。
super不能使用在静态方法中。
“ super. ”大部分情况下是可以省略的。什么时候不能省略呢?
-- 如果父类型和子类型中有同名属性,并且希望在子类对象当中访问父类,要用super.
super()只能出现构造方法第一行,通过当前的构造方法去调用父类中的构造方法。目的是创建子类对象的时候,先初始化父类特征。
2.super()
表示通过子类的构造方法调用父类的构造方法,符合现实世界中要想有儿子先得有父亲的情况。
3.当一个构造方法第一行既没有this()又没有super()时,会默认有一个super();表示通过当前子类的构造方法调用父类的无参数构造方法,所以必须保证父类的无参数构造方法存在。
4.this()和super()不能共存
5.父类的构造方法一定会执行
建议在类中将无参数构造方法写出来
//001
class A{
public A(){
System.out.println("A invoke");
}//无参构造方法
}
class B extends A{
public B(){
System.out.println("B invoke");
}
}
//主方法
public class Test{
new B();
/*输出结果:
A invoke
B invoke*/
//由此可见 构造B(子类)时先调用A(父类)的构造方法
}
//002
class A{
public A(int i){
}//当有有参构造方法时,无参数构造方法就没有了
}
class B extends A{
public B(){
//这里省略了super(),会调用父类的无参数构造方法
System.out.println("B invoke");
}
}
//主方法
public class Test{
new B();
/*输出结果:
注意:这时会报错!
*/
}
p468
super. 属性名 【访问父类的属性】
super . 方法名(实参) 【访问父类的方法】
super(实参) 【调用父类的构造方法】
IDEA
-
idea的组织方式:project -> module
-
创建module :file菜单 --> new --> module --> 在new module上点击左上角的Java --> next --> 给module起一个名字
-
在src目录下新建类:右击src – new – Java class – 填写class名字
-
之后就可以写代码运行喽~~~(右键 --> run 或者直接点绿色的三角形)
-
设置字体 : file --> settings --> font
-
自动生成main方法:psvm然后回车
-
快速生成System.out.println():sout回车
-
idea自动保存:不需要CTRL + S
-
CTRL + Y 删除一行代码
-
CTRL + D 复制 一行代码
-
退出任何窗口,都可以使用esc键盘;新建任何东西,都可使用快捷键alt + insert , 然后输入你要建的东西
-
窗口变大变小:CTRL + shift + F12
-
切换Java程序 :从一个类到另外一个类 – alt + 左右箭头
-
运行:CTRL + shift + F10
-
切换窗口 : alt + 窗口标号
-
CTRL + P可以查看所用方法里可以传递什么参数
-
按home键鼠标到最开始,end键鼠标到末尾
-
多行注释:CTRL + shift + 斜线
-
单行注释:CTRL + 斜线
-
想要查看某个方法:按住CTRL,出现蓝色下划线
-
shift敲两下会出现搜索框
*Ctrl + O 重写方法
抽象类abstract
1.抽象类无法实例化,无法创建对象。
抽象类是将有共同特点的类和类进一步抽象形成从抽象类。由于类本身是不存在的,所以抽象类无法创建对象。
2.抽象类和抽象类能不能进一步向上抽象?
银行账户类和会员类可以进一步抽象为账户类。
3.抽象类属于引用数据类型。
4.语法格式:
【修饰符列表】abstract class 类名{ }
5.抽象类是无法实例化,无法创建对象的,所以抽象类是用来被子类继承的,所以abstract前绝对不能加final,否则无法被继承。那么子类就可以实例化对象,但是子类前也可以加一个abstract。
6.虽然无法实例化,但仍有构造方法,这个构造方法供子类使用,因为子类的构造方法中会默认有super()
7.抽象类关联抽象方法:抽象方法表示没有实现的方法,没有方法体的方法。例如
public abstract void doSome();没有大括号
抽象类中不一定有抽象方法,也可以存在非抽象方法。但抽象方法只能出现在抽象类中
8.面试题:Java中凡是没有方法体的方法都是抽象方法。×
Object类中有很多native修饰的方法,调用C++的动态链接库程序
//定义一个抽象类
abstract class Animal{
//给一个抽象方法
public abstract void move();
}
//子类:非抽象的,会继承父类中的抽象方法,而抽象方法只能出现在抽象类中,所以会报错。
//应该对父类中的抽象方法重写
class Bird extends Animal{
//覆盖、重写,也可以叫做实现
public void move( ) { }
}
//使用多态
public class AbstractTest{
public static void main(String[] args){
Animal a = new Bird();
//这就是面向抽象编程,降低程序耦合度,提高程序扩展力
}
接口
1.接口也是一种引用数据类型,编译后生成的也是class文件
2.接口时完全抽象的(抽象类是半抽象的),也可以说接口是特殊的抽象类,接口创建不了对象
3.接口语法:
【修饰符列表】 interface 接口名{ }
4.接口和接口可以继承,并且接口支持多继承,一个接口可以继承多个接口
interface C extends A,B
5.接口中只包含两部分内容:常量(public static final int a = 1 , 因为默认都是常量 , 所以可以省略成int a = 1;) +== 抽象方法== (public abstract int sum();), 且都是公开的,都由public修饰。因为接口中的方法都是抽象方法(不能带有方法体),在写代码时可将public abstract省略 。访问的时候直接“接口名.”的方法
类和接口
1.类和类之间叫做继承,类和接口之间叫做实现,用implements关键字完成(和extends差不多)
2.当一个非抽象的类实现接口的话,必须将接口中所有的抽象方法全部实现。在重写方法的时候,要求访问权限不能更低,而interface中的方法都是public,所以重写的方法前必须加public
接口和接口之间支持多继承,那么一个类可以同时实现多个接口吗?可以滴~每个接口中的方法都要重写。这种机制弥补了类和类只能单继承的缺陷
接口与接口在进行强制类型转化的时候,没有继承关系,也可以强转,但在运行时可能会出现ClassCastException异常,所以向下转型要加instanceof判断
继承和实现都存在的话,代码怎么写?
–继承在前,实现在后
接口在开发中的作用,类似于多态在开发中的作用。
多态:面向抽象编程,不要面向具体编程,降低程序的耦合度,提高程序的扩展力。而接口正好就是完全抽象的,所以可以说成面向接口编程
public class Master{
public void feed (Dog d){}
public void feed (Cat c){}
//假设又要养其他的宠物,那么这个时候需要再加一个方法,需要修改代码
//这样扩展力太差,违背了OCP原则--对扩展开放,对修改关闭
}
public class Master{
public void feed (Animal a){}
//面向animal父类编程,父类是比子类更抽象的
//多以我们叫做面向抽象编程,不要面向具体编程,这样程序的扩展力就强
}
customer has a foodmenu!凡是能用has a 来描述的,统一以属性的方式存在。
cat is a animal。凡是满足is a 的都可以设置为继承。
cooker is like a foodmenu。凡是满足is like 的可以用实现来定义
public class Test{
public static void main(String[] args){
FoodMenu cooker1 = new ChinaCooker();
Customer customer = new Customer(cooker1);
customer.order();
}
}
public interface FoodMenu{
void jiDan();
void rouSi();
}
public class ChinaCooker implements FoodMenu{
public void jiDan(){
System.out.println("中国人的西红柿炒鸡蛋!");
}
public void rouSi(){
System.out.println("中国人的鱼香肉丝(我饿了)!");
}
}
public class AmericanCooker implements FoodMenu{
public void jiDan(){
System.out.println("歪果的西红柿炒鸡蛋!");
}
public void rouSi(){
System.out.println("歪果人的鱼香肉丝(我饿了)!");
}
}
public class Customer{
private FoodMenu foodMenu;
//构造方法
public Customer(){
}
public Customer(FoodMenu foodMenu){
this.foodMenu = foodMenu;
}
//setter and getter
public void setFoodMenu(FoodMenu foodMenu){
this.foodMenu = foodMenu;
}
public FoodMenu getFoodMenu(){
return foodMenu;
}
//提供一个点菜方法
public void orderf(){
foodMenu.jiDan();
foodMenu.rouSi();
}
}
任何一个接口都有调用者和实现者,接口可以将调用者和实现者解耦合
抽象类是半抽象的,接口时完全抽象的。
抽象类中有构造方法,接口中没有构造方法。
接口和接口之间支持多继承,类和类之间只能单继承。
例题:
设计一个笔记本电脑类。
设计一个可插拔的接口InsertDrawable。
设计一个鼠标类,实现InsertDrawable接口,并实现方法 。
设计一个键盘类,实现InsertDrawable接口,并实现方法 。
在笔记本电脑类中有一个InsertDrawable接口属性,可以让笔记本电脑可插拔鼠标、键盘。
object–老祖宗
所有的类默认继承object,object中的方法所有类通用。
tostring方法
返回字符串:类名 + @ + 对象地址的十六进制
往往将该方法重写
输出引用的时候,会自动调用tostring方法
equals方法
public boolean equals(Object obj){
return(this == obj);
}
用来判断两个对象是否相等。
但是equals源代码中使用的仍然是“==”,所以判断的仍然是两个对象的地址是否相同,所以我们往往要将equals重写!
public boolean equals(Object obj){
//获取第一个的日期
int year1 = this.year;
int month1 = this.month;
int day1 = this.day;
//获取第二个的日期
//不能写int year2 = obj.year;
//访问子类中特有的,向下转型
if(obj instanceof Mytime){//null isnot istanceof 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 ||!(obj instanceof Mytime)
return false;
if(this == obj)//地址相同
return true;
Mytime t = (Mytime) obj;
if(this.year = t.year && this.month = t.month && this.day = t.day2)
return true;
return false;
判断字符串相等必须用equals()
输出引用的时候,会自动调用tostring方法
String类中的tostring和equals方法已经重写
finalize()
了解即可,已过时
1.在object类中的源代码:
protected void finalize ( ) throws Throwable { }
finalize()方法只有一个方法体,里面没有代码,而且由protected修饰。
2.这个方法不需要程序员手动调用,JVM的垃圾回收器(GC:garbage collection)负责调用这个方法。 --不需要调,只要重写
3.执行时机:当一个Java对象即将被垃圾回收器回收的时候,垃圾回收器负责调用finalize方法。
4.finalize方法实际上是sun公司为Java程序员准备的一个垃圾销毁时机。如果希望在对象销毁时机执行一段代码的话,这段代码要写到该方法中。回顾静态代码块,static{ } 在类加载时刻执行,并且只执行一次。(一个人在即将离开世界的时候做的事--遗嘱)
5.垃圾回收器不是轻易启动的,可能垃圾太少,也可能时间没到。
System.gc();//建议启动垃圾回收器,只是建议,可能不启动,也可能启动,启动的概率变大。
hashcode()
1.源代码:public native int hashcode();
不是抽象方法,带有native关键字,底层调用C++
2. 返回哈希码,实际上就是一个Java对象的内存地址经过哈希算法得出的一个int值。所以hashcode()的结果可以等同于一个Java对象的内存地址。
匿名内部类
内部类几乎不用,匿名内部类可能要用.
不建议使用匿名内部类,因为一个类没有名字,没有办法重复使用,另外代码太乱,可读性太差。
1.内部类:在类的内部又定义了一个新的类。
内部类的分类:静态内部类、实例内部类、局部内部类。匿名内部类属于局部内部类,因为这个类没有名字而得名
使用匿名内部类之后,代码可以省去class ComputeImp1这个类。
使用匿名内部类,表示ComputeImp1这个类没名字了。
mm.mySum(new Compute() { } , 100 , 200);
interface无法new对象,上面加了花括号表示对接口的实现!
mm.mySum(new Compute() {
public int sum (int a , int b){
return a + b;
} , 100 , 200);
数组array
1.Java中的数组是一种引用数据类型,不属于基本数据类型。存取在堆内存中。
2.数组一旦创建,长度不可改变。
3.所有的数组对象都有length属性(自带的),用来获取数组中元素的个数。
4.数组中元素内存地址连续。首元素的内存地址作为整个数据对象的内存地址
5.数组缺点:增删的时候复杂;不能存储大数据量,因为很难找到一块特别大的连续内存空间。
1,定义一个一维数组
int [] array;
2,初始化一个一维数组
静态初始化:int [] array = { 1 ,2} ;
动态初始化:int [] array = new int[5]; //初始化一个5个长度的int类型数据,每个元素默认值0
3.如果直接传一个静态数组,可以这样写printArray( new int [] { 1,2,3} ) []中不能写数字
main方法中的String[] args
JVM默认传递过来的这个数据对象的长度是0;
其实这个数组是留给用户的,用户可以在控制台上输入参数,这个参数自动会被转化为String [] args
一维数组扩容
在Java开发中,数组长度一旦确定不变,数组满了怎么办?
需要扩容–先新建一个大容量的数据,然后将小容量数组中的数据一个一个拷贝到大数据中,原先的数组被销毁。
因为涉及到拷贝的问题,数组扩容效率较低,最好在创建数组的时候一次性到位。
拷贝函数System.arraycopy()
当为引用数组时,拷贝的是对象地址。
二维数组
二维数组是一个特殊的一维数组,特殊在这个一位数组中的每一个元素是一个一维数组。
int [ ][ ] a = { {1 , 2} , { 3 , 4 } , {5} }; – a.length = 3
int [ ][ ] a =new int[3] [4];//三行四列
Arrays
常用的两个方法:二分查找和排序。
二分法常用来判断数组中是否有某个元素。
String
规定双引号括起来的字符串是不可变的。并且在JDK中,字符串都是直接存储在“方法区”的“字符串常量池”当中的,为什么呢?因为字符串在实际的开发中使用太频繁,为了执行效率。
String s1 = "abcdef";
String s2 = "abcdef" + "xy";
没有直接堆s1进行修改,而另外在字符串常量池中开辟了两个空间
String s3 = new String("xy");
两种创建字符串的方式是不一样的,第一种直接指向字符串常量池,第二种会在堆内存中创建对象(只要是new的对象都在堆中。注:垃圾回收器是不会释放常量的。
两者最终都会指向字符串常量池中的某个位置,只不过在于堆内存中有无对象。
-------------------------------------------
String s1 = "hello";
String s2 = "hello";
s1和s2地址相同,即s1==s2
-------------------------------------------
String s1 = new String(“xyz”);
String s2 = new String(“xyz“);
s1和s2地址不相同
-------------------------------------------
为了以防万一,我们在比较字符串时都使用equals方法
tips:为了防止使用equals时出现空指针异常,我们常常使用这种格式--"teststring".equals(k);
String中的构造方法
最常用的一种方式: String s1 = “hello world”;
byte[ ] bytes = {97,98,99};
String s2 = new String(bytes); //s2 = "abc"对应ASCII码
String s3 = new String(bytes,1,2); //s3 = “bc”(字节数组,数组元素下标的起始位置,长度)
char[ ] chars = {‘我’,‘爱’,‘小’,‘马’};
String s4 = new String(chars); //s4 = “我爱小马”
同理可将chars中部分转为字符串
String中的常用方法
char c = string.charAt(int index);//返回string中index下标处的字符
int a = "abc".compareTo("abc");//a = 0 前后一致
int b = "abcd".compareTo("abce");//b = -1 前小后大
int c = "abce".compareTo("abcd");//a = 1 前大后小
boolean contains(charSequence s)//判断字符串中是否含有某个字符(串)
boolean endsWith(String s)//判断字符串中是否以某个字符(串)结尾
boolean startsWith(String s)//判断字符串中是否以某个字符(串)开始
boolean equalsIgnoreCase(String s)//判断两个字符串是否相等并且忽略大小写
byte[ ] getBytes()//将字符串对象转换成字节数组
char[ ] toCharArray()//将字符串对象转换成字符数组
int indexOf(String str)//判断某个子字符串在当前字符串中第一次出现处的索引
int lastindexOf(String str)//判断某个子字符串在当前字符串中最后一次出现处的索引
boolean isEmpty()//判断某个字符串是否为空
String s = " ";//为空 true
String b = null; //出现空指针异常
String replace(string target , string replacement);
String[ ] split(String regex)//拆分字符串
"2004-07-18".split("-");
String substring(int beginIndex)//截取字符串
String substring(int beginIndex , int endIndex)//截取字符串,从beginIndex到endIndex-1处
String toLowerCase()//全部转化为小写
String toUpperCase()//全部转化为大写
String trim();//去除字符串前后空白,中间的空白去不了
String中只有一个方法是静态的,不需要new对象,这个方法叫做valueof,将非字符串转化成字符串
println调用的方法就是valueof,由此可见,可以在控制台上打印的东西都是一个字符串
String s1 = String.valueof(true);//s1 = "true"
String s2 = String.valueof(new Customer());//参数是一个对象的时候,会调用toString方法,如果没有重写该方法,即s2为对象内存地址
为什么String是不可变的?
源代码中String有一个byte数组,并且这个数组是使用final修饰的。因为数组一旦创建长度不可变,而且被final修饰的引用一旦指向某个对象后,不能再指向其他对象,所以String是不可变的。
StringBuffer和StringBuilder
当需要频繁的拼接字符串时,会在方法区中开辟许多空间,造成空间的浪费,为了避免这种情况,我们可以使用StringBuffer和StringBuilder,没有被final修饰。
往StringBuffer中放字符串实际上是放到了byte数组中,byte[]的初始容量为16,超过16会自动扩容,底层调用了数组拷贝的方法System.arraycopy(),扩容后原来的数组会被垃圾回收器回收。
StringBuffer a = new StringBuffer();
a.append("a");
a.append("b");
a.append("c");
a.append(3.14);
System.out.println(a);//a = "abc3.14"
//优化StringBuffer的性能
//在创建StringBuffer的时候尽可能给定一个初始化容量,减少扩容过程。
//StringBuffer和StringBuilder的区别,StringBuffer中的方法都有synchronized关键字修饰,表示在多线程环境下运行是安全的
//而StringBuilder中没有该关键字,表示在多线程环境下运行是不安全的。
//--stringbuffer是线程安全的
八种包装类型
1.Java中为8种基本数据类型又准备了8种包装类型,8种包装类属于引用数据类型,父类是object
2.为什么会有这种设置?请看代码
public static void doSome(Object obj){
System.out.println(obj);
}
public static void main(String[] args){
//有没有这种需求,调用doSome()方法的时候需要传一个数字进去。
//但是数字属于基本数据类型,不继承object,无法作为参数传入。
//那怎么办呢,可以传一个数字对应的包装类进去--即将数字包装成一个对象传进去
八种基本数据类型对应的包装类型名:
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)
//将基本数据类型转化为引用数据类型(装箱)
Integer i = new Integer(123);
也可以传一个字符串进去
Integer i = new Integer(“123”);
//将引用数据类型转化为基本数据类型(拆箱)
float f = i.floatValue();//f = 123.0
int ret = i.intValue();//ret = 123
3.Number类是一个抽象类,无法实例化对象
4.Integer包装类
构造方法–传int或String进去;
Integer a = new Integer(“中文”);
//java.lang.NumberFormatException–著名报错:数字格式化异常
常量值:Integer.MAX_VALUE,Integer.MIN_VALUE
自动装箱&&自动拆箱
Integer x = 100;
int y = x;
System.out.println(x + 1);// + 要求两边是基本数据类型的数字,x是包装类,不属于基本数字类型,运算时会自动拆箱。
//只有在 + - * / 时会自动拆箱
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b);//false 不会自动拆箱 比较的是地址
//奇妙的事情发生了!
Integer a = 128;
Integer b = 128;
System.out.println(a == b);//false
Integer a = 127;
Integer b = 127;
System.out.println(a == b);//true
Java中为了提高程序的执行效率,将[-128,127]之间所有的包装对象提前创建好,放到了方法去中的整数型常量池当中,
目的是只要用这个区间的数据不需要再new了,直接从整数型常量池当中取出来。
Integer类中常用的方法
Static int parseInt(String s) 字符串转化为数字,同理当字符串不是数字的时候,会出现报错NumberFormatException
静态方法,使用类名调
int value = Integer.parseInt("123");
类似的,有:
double ret = Double.parseDouble("3.14");
int&Integer&String之间的转化
Java中对日期的处理
java.util.Date
其中已经对toString方法重写
Date nowTime = new Date();//获取当前精确到毫秒的系统时间,直接调用无参数构造方法即可
System.out.println(nowTime);//调用toString方法,显示日期
当new Date(long date) 构造方法中有参数时,表示自1970年1月1日 00:00:00 000以来经历了date毫秒数后的日期,如:
Date time = new Date(1); //1970年1月1日 00:00:00 001(标准时间),其他地方的标准时间会有时差
//获取昨天此时的时间
Date time = new Date(System.currentTimeMillis() - 1000 * 60 * 60 *24);//一秒等于一千毫秒
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
String time = sdf.format(time);
将日期格式化:
在java.text.SimpleDateFormat包下有一个SimpleDateFormat类 ,专门进行日期格式化。
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”);
//new对象,括号中传入的字符串时日期格式
String time = sdf.format(nowTime);
yyyy 年
MM 月
dd 日
HH 时
mm 分
ss 秒
SSS 毫秒
以上是将日期变为字符串,那么我们如何将日期字符串转化为Date类型?
Sting time = “2004-07-18 08:08:08 888”;
SimpleDateFormat sdf2 = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”)//格式不能乱写,要和上面一样,否则出现异常
Date datetime = sdf2.parse(time);
总结System中的方法
System.out【out是system类的静态变量】
System.out.println9) 【println()方法不是System类的,是printStream类的方法】
System.gc()建议启动垃圾回收器
System.currentTimeMillis() 获取自1970年1月1日到系统当前时间的总毫秒数
获取自1970年1月1日 00:00:00 000到当前系统时间的总毫秒数,已知一秒=1000毫秒
System.currentTimeMillis();//静态方法
有什么用呢?
需求:统计一个方法执行所耗费的时长
可以在调用之前记录一个毫秒数,在结束之后再调用一次。
System.exit(0) 退出JVM
数字的格式化(了解即可)
//java.text.DecimalFormat专门负责数字格式化
//DecimalFormat df = new DecimalFormat(“数字格式”);
#表示任意数字
,表示千分位
. 表示小数点
0 表示不够时补0
DecimalFormat df1 = new DecimalFormat(“###,###.##”);
String s1 = df1.format(1234.56789); // s = 1,234.56
DecimalFormat df2 = new DecimalFormat(“###,###.0000”);
String s2 = df2.format(1234.56); // s = 1,234.5600
BigDecimal
BigDecimal属于大数据,精度极高,不属于基本数据类型,属于Java对象(引用数据类型),这是SUN提供的一个类,专门用在财务软件中。在java.math包下。
BigDecimal v1 = new BigDecimal(100);
BigDecimal v2 = new BigDecimal(200);
//求和不能直接用v1 +v2,因为v1 v2都是引用,要使用add方法
BigDecimal v3 = v1.add(v2);
随机数
import java.util.Random;
//创建随机数对象
Random ran = new Random();
//随机产生一个int类型取值范围内的数字
int num1 = ran.nextInt();
int num2 = ran.nextInt(101);//101为边界,表示随机产生一个[0-100]之间的数字
枚举类型
当一个方法最后返回结果有两种情况时,我们可以用布尔类型表示两种结果,但是在项目中我们会遇到一个方法最后返回的结果有三种情况、四种情况甚至更多,并且结果我们都已明确且可以枚举出来,此时我们可以用枚举类型来改造程序。
枚举编译之后也生成class文件,枚举也是一种引用数据类型,枚举中的每一个值都可以看作是常量。
enum Result
{
SUCCESS,FAIL//直接写单词,不能赋值,没有分号,不同单词之间用逗号隔开
}
public Result divide(int a, int b)
return Result.SUCCESS;
return Result.FAIL;
Result r = divede(10,0);
System.out.println(r == Result.SUCCESS? "成功“:”失败“;);
异常
1.什么是异常,异常机制有什么用?
在除法分母分母为零时,会出现异常。Java提供了异常的处理方式,将异常信息打印输出到控制台,供程序员参考,程序员看到异常信息后可以对程序进行修改,让程序更加健壮。
2.Java语言中,异常是以什么形式存在的?
异常在java中以类的形式存在,每一个异常类都可以创建异常对象。
int a = 10;
int b = 0;
int c = a / b;
//JVM在执行到此处时,会new异常对象:new ArithmeticException("/by zero");然后把异常抛出去,打印输出到控制台上。
3.Java的异常处理机制包括两种方式
第一种:在方法声明的位置上使用throws关键字【异常上抛】--老板解决
如果异常发生后一直无法解决向上抛,最后抛给main方法,再向上抛给JVM,那么最后只有一个结果,程序终止,不会再往下执行。所以一般不建议在main方法上throws
第二种:使用try、catch语句进行异常的捕捉【异常捕捉】--自己解决
public static void doSome() throws xxxx(编译时异常)
{
//因为xxxx为编译时异常,所以必须对xxxx异常进行预先的处理,否则编译器报错
}
第一种:再方法调用的位置上继续使用throws来完成上抛,抛给调用者,在抛出时可以抛出异常类的父类,不能抛出比他小的,直接抛出Exception也可,抛出多个异常用逗号隔开也可以
public static void main(String [] args) throws xxxx
{
doSome();
}
第二种:
public static void main(String [] args) throws xxxx
{
try
{
doSome();
}
catch()
{
}
}
4.只要异常没有捕捉,采用上报的方式,此方法后面的代码不会执行。另外需要主义,try中的某一行出现异常,后面的代码不会执行。catch捕捉异常后,后续的代码可以执行
Error 和 Exception 都是Throwable的子类, 在java中只有Throwable类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型.:
异常分为 编译时异常(可检测到的异常) 和 运行时异常
而编译时异常才是需要处理的异常
运行时异常是我代码有问题 比如空指针 索引越界等等 需要修改代码
错误是一般处理不了的 比如硬件不行 内存溢出 等等 都报错误;
1, Exception(特例)是程序正常运行中,可以预料的意外情况,可以被捕获,进行相应的处理.。所有RuntimeException及子类都被称为运行时异常。所有Exception的直接子类都被叫做编译时异常,编译时异常并不表示发生在编译阶段,表示在编译时必须预先对这种情况处理,否则编译器报错。所有的异常都发生在运行阶段。
2.Error(错误) 是指正常情况下,不大可能出现的情况,绝大部分的Error 都会导致程序处于非正常的,不可恢复的状态,即退出程序, 不需要捕获, 常见的OutOfMemoryError 是Error的子类.
运行时异常发生概率较低,又被叫做未受检异常、未受空异常。编译时异常发生概率较高,又被叫做受检异常、受空异常。
假设java中没有对异常进行划分,没有分为运行时异常和编译时异常,会是怎样的效果呢?
所有的异常都需要在编写程序阶段对其进行预处理,代码绝对安全,但是编写程序太累,代码到处都是处理异常的代码。
1.catch后面的小括号中的类型可以是本类型也可以是夫类型-多态
2.catch可以写多个,建议catch写具体一些,这样有利于代码的调试。但是catch后面的类型要从小到大排,否则后面的catch语句执行不了。
3.catch括号后可采用或的语句 | 一个竖杠
异常对象的两个重要方法
//获取异常简单的描述信息
String msg = exception.getMessage();
//打印异常追踪的堆栈信息--异步线程的方式
exception.printStackTrace();
从上往下看,不看sun公司的异常
catch里一定要打印异常追踪信息,否则出了问题也不知道
try、catch中的finally子句
1.在finally中的代码是最后执行的,并且是一定会执行的,即使try语句中出现了异常
2.finally必须和try一起出现,不能单独编写
3.通常在finally语句中完成资源的关闭
4.没有catch的语句;try不能单独使用,try、catch可以联合使用
try{
System.out.println(“try…”);
return;
}finally{
System.out.println(“finally…”);//这里能执行
}
System.out.println(“hello word”);//这里不执行
如果把return换成System.exit();那么finally也不会输出了
5.常见的一道题
public static int m()
{
int i = 100;
try{
return i;
}finally{
i++;
}
}
int result = m(); //result = 100?!
需要遵守的两个规则:
方法体中的代码必须遵循自上而下的顺序逐行执行;return语句 一旦执行,整个方法体必须结束。
6.final&finally&finalize
final int i = 10;
try{} finally{}
final和finally都是关键字
finalize()是object类中的一个方法,所以finalize()是标识符。–已过时
7.重写之后的方法不能比之前的方法抛出更多的异常,可以更少、可以更小
自定义异常
如何自定义异常?
1.编写一个类,继承Exception(--编译时异常)或者RuntimeException(--运行时异常)
2.提供两个构造方法,一个无参数的,一个带有String参数的
public class MyException extends Exception{
public MyException(){
}
public MyException(String s){
super(s);
}
集合Collection(单键) + Map(键对)
1.什么是集合?
数组其实就是一个集合,集合实际上就是一个容器,可以容纳其他类型的数据
2.集合在开发中的作用:
在实际开发中,假设数据库中要取十个数据,会把这十个数据封装成十个对象,然后将这十个对象放到集合当中,再将集合传到前端,然后遍历集合,将数据展现出来。
3.集合中不能直接存储基本数据类型,也不能存储对象。实际中存储的都是java对象的内存地址,或者说引用。集合也是一个对象。
4.不同的集合,底层会对应不同的数据结构。使用不同的集合等同于使用了不同的数据结构。所有的集合类和集合接口都在java.util包下
5.在java中集合分为两大类
一类是单个方式存储元素,这一类集合中超级父接口java.util.Collection
一类是以键值对儿的方式存储元素,这一类集合中超级父接口java.util.Map
6.所有集合都是可迭代的
Iterator it = "Collection 对象“.iterator();
it是迭代器对象
Collection
SortedSet集合由于继承了Set集合,所以也是无序不可重复的,但是放在SortSet集合中的元素可以自动按照大小排序。注意,可排序不等于有序,有序是指拿出来的顺序和放进去的顺序是一样的。
放到HashSet和TreeSet中的元素就相当于HashMap和TreeMap中的key
Map
TreeMap中的key可以自动按照大小顺序排序。
Map集合的key,就是一个Set集合
Colletion中常用方法
1.Collection中能存放什么元素?
--在没有使用泛型之前,可以存放Object的所有子类型;使用了泛型之后,只能存放某个具体的类型。(集合中不能直接存放基本数据类型,也不能存放Java对象,存放的都是内存地址)
2.向集合中添加元素: boolean add(Object e)--会自动装箱
3.获取集合中元素的个数: int size()
4.清空集合: void clear()
5.判断是否包含某个元素: boolean contains(Object e)
Collection c = new ArrayList();
String s1 = new String("abc");
String s2 = new String("def");
c.add(s1);
c.add(s2);
String x = new String("abc");
c.contains(x); --true //contains底层会调用equals方法
6.删除某个元素: boolean remove(Object e)
Collection cc = new ArrayList();
String s1 = new String("hello");
String s2 = new String("hello");
cc.remove(s2);
//cc.size() = 0 remove底层会调用equals方法
7.判断集合是否为空: boolean isEmpty()
8.把集合转化成数组: Object[] toArray()
Iterator–Collection及子类可使用
//创建集合对象
Collection c = new ArrayList();
//添加元素
c.add("hello");
c.add("woaixiaoma");
c.add(520);
c.add(new Object());
//遍历
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
//第二步:通过迭代器对象遍历集合
//迭代器中的方法
//boolean hasNext() 如果仍有元素可以迭代,则返回true。迭代器最初并没有指向第一个元素。
//Object next() 返回迭代的下一个元素
while(it.hasNext())
{
Object obj = it.next();
System.out.println(obj);
}
注意:一旦集合结构发生改变(即添加元素或删除元素等),迭代器必须重新获取,所以Collection和Iterator中的remove方法是不同的!!!
迭代器去删除时,会自动更新迭代器,并且更新集合
集合去删除,但是没有更新迭代器,迭代器不知道集合变化
List中特有方法
继承Collection中的方法,这里不再赘述
1.void add ( int index , E element) //在下标为index的位置插入元素,其他元素往后移
2.E get ( index )//根据下标取元素,对应原理--List中的元素都有下标
List集合特有的遍历元素方式
for ( int i = 0 ; i < mylist.size() ; i++)
{
Object o = mylist.get( i ) ;
System.out.println(o);
}
3.int indexOf ( Object o )//获取指定对象第一次出现初的索引
4.int lastIndexOf ( Object o )//获取指定对象最后一次出现处的索引
5.E remove ( int index )//删除指定下标处的元素
6.E set ( int index , E element)//修改指定位置上的元素
ArrayList
1.ArrayList当使用无参构造时,初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
List mylist = new ArrayLisr(20);//给定容量的有参构造
System.out.println(mylist.size());//0
可见size()方法获取的时元素的个数,不是获取集合容量
2.特殊的构造方法
//创建一个HashSet集合
Collection c = new HashSet();
c.add(100);
c.add(11);
//通过这个构造方法可以将Hashset集合转换成List集合
List mylist = new ArrayList(c);
3、怎么将一个线程不安全的ArrayList集合转换为线程安全的呢?
--使用集合工具类 java.util.Collections;
PS:java.util.Collection是集合接口 注意区分
List myList = new ArrayList();
Collections.synchronizedList(myList);//myList就变成线程安全的了
Vector
1、底层也是一个数组,初始化容量为10.扩容时double
2、Vector中所有方法都带有synchronized关键字,是线程安全的,效率低,使用少。
Map<K,V>中常用方法
java.util.Map中常用的方法
Map集合以key和value的方式存储数据,key和value都是引用数据类型,并且都存储对象的内存地址。其中
key起到主导的地位,value是key的一个附属品。
1、void clear()//清空集合
2、boolean conrainKey(Object key)
boolean conrainKey(Object value)
3.V get (Object key)//通过key获取value
4.V put(K key, V value) //向Map集合中添加键值对
5.Set<K> keySet() //获取Map集合所有的key
Collection<V> values()//获取Map集合所有的value
6.V remove (Object key)//通过key删除键值对
7.int size()
8.Set <Map.Entry<k,V>> entrySet()//将Map集合转换成Set集合
Map.Entry和String一样,都是一种类型的名字,只不过Map.Entry是静态内部类
遍历MAP
MAP没有继承Collection,是否还能遍历元素呢,答案当然是可以滴~
方法一:获取所有的Key,通过遍历Key,来遍历Value
方法二:Set <Map.Entry<k,V>> entrySet()
//将Map集合转换成Set集合,集合中元素类型是Map.Entry
第二种方法效率更高
HashMap
1、HashMap集合底层是哈希表/散列表的数据结构
2、哈希表是一个数组+单向链表的数据结构--是一个一维数组,其中每一个元素是一个单向链表。链表中的每一个节点有key、hash值、value和下一个节点的内存地址。
3、HashMap中数组初始容量为16,默认加载因子是0.75,表示当数组容量达到自身的75%后,会自动扩容。官方推荐HashMap初始化容量必须是2的倍数,这是达到散列均匀,提高提取效率所必需的。
4、在JDK8之后,若哈希表单向链表上的元素超过8个,会把单向链表转换为红黑树这种数据结构,如果红黑树的节点个数小于6个,又会转换成单向链表。
5、HashMap中key、value部分都可以为空。
放在HashMap集合key部分的元素和放在HashSet集合中的元素需要重写hashCode()方法和equals ()方法
Hashtable & Properties
1、Hashtable中key、value部分都不可以为空。
2、HashTable是线程安全的,效率较低,不怎么用,让我们来看看Properties
3、初始化容量为11,加载因子0.75,扩容时为新容量=2*旧容量+1
****************************************************************************************
1、Properties是一个Map集合,继承Hashtable,key和value都是String类型。被称为属性类对象。
2、创建一个Properties对象
Properties pro = new Properties();
3、需要掌握的两个方法:存+取
pro.setProperty("name",:lu");//存,两个位置必须都是字符串
String s = getProperty("name");取,通过key得到value
TreeMap
1、TreeMap底层数据结构是二叉树
2、放到TreeSet中的元素,相当于放到TreeMap中的key部分。TreeSet集合中的元素无序不可重复,但是可以自动从小到大排序,称为可排序集合。
3、对于自定义类型,TreeMap也能自动排序吗?--答案是不行滴
方法一:
自定义类实现comparable接口,否则会出现向下转型异常
放在TreeSet集合中的元素必须实现java.lang.Comparable接口,并且实现compareTo方法,equals可以不实现
方法二:
自定义比较器,比较器中有比较方法,在构造方法的时候传入。
比较器要实现java.util.Comparator(Comparable是java.lang包下的)
TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
//使用匿名内部类的方式
//这个类没有名字,直接new接口,后面跟大括号表示实现
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>(){
public int compare(WuGui o1, WuGui o2)
{
return o1.age - o2.age;
}
});
当比较规则不会发生改变的时候,建议实现comparable接口
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口
集合工具类
java.util.Collection集合接口
java.util.Collections集合工具类
//ArrayList集合不是线程安全的
List<String> list = new ArrayList<>();
Collections.synchronizedList(list);//变成了线程安全的
Collection.sort(list);//排序,只能传入List类型集合,同样要求实现了Comparable接口
UML图画法
链接: link
泛型
JDK5.0之后推出的新特性,见p689
好处:
1.集合中存储的元素类型统一了
2、从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
缺点:
集合中存储的元素缺乏多样性
使用泛型List<Animal>之后,表示List集合中只允许存储Animal类型的数据
List <Animal> myList = new ArrayList<Animal>();
Iterator<Animal> it = myList.iterator();//表示迭代器迭代的是Animal类型
JDK8之后引用了自动类型推断机制,又称为“钻石表达式”
我们可以省略后面的尖括号里的类型,这样写:
List<Animal> myLsit = new ArrayList<>();
自定义泛型
public class Test<E>{
public void doSome(E o){
System.out.println(o);
}
public static void main(String[] args){
//new对象的时候指定了泛型是String类型
Test<String> a = new Test<>();
//类型不匹配
//a.doSome(100);
a.doSome("abc");
}
}
增强for循环-foreach
JDK5之后推出的新特性
语法格式:
for(元素类型 变量名 : 数组或集合){
System.out.println(变量名);
}
int[] arr = {1,2,3,4,5,6};
for(int i : arr){
System.out.println(i);
}
缺点:没有下标
IO流
通过IO可以完成硬盘文件的读和写
IO流的分类:
按照流的方向进行分类
以内存作为参照物
往内存中去,叫做输入,或者叫做读
从内存中出来,叫做输出,或者叫做写
按照读取数据方式不同进行分类
字节流:按照字节的方式读取数据,一次读取一个字节,
--这种流是万能的,什么类型的文件都可以读取
字符流:按照字符的方式读取数据的,一次读取一个字符,
--这种流是为了方便读取普通文本文件(txt)而存在的,连word都不能读取(ord 文档不是纯文本文件,除了文字还包含很多格式信息,因此不能用字符流操作。)。
IO流有四大首领,每个首领都是抽象类:
java.io.InputStream//字节输入流
java.io.Outputstream//字节输出流
java.io.Reader//字符输入流
java.io.Writer//字符输出流
//后缀为Stream的都是字节流,后缀为Reader、Writer的都是字符流
所有的流都实现了Closeable接口,都是可关闭的,都有close()方法
所有的输出流都实现了Flushable接口,都是可刷新的,都有flush()方法。输出流在最终输出之后,一定要记得刷新一下,表示将管道中未输出的数据全部写到文件当中,否则可能丢失数据。
java.io下需要掌握的十六个流:
//文件专属
java.io.FileInputStream(掌握)
java,io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter
*******************FileInputStream*****************
fis = new FileInputStream("tempfile");
byte[] bytes = new byte[4];
int readCount = 0;
while((readCount = fis.read(bytes)) != -1)
{
System.out.println(new String(bytes , 0 , readCount);
}
int available()//返回流当中剩余的没有读到的字节数量
这个方法有什么用,请看例子:
byte[] bytes = new byte[fis.available()];
这样后面读就不用循环了,这种方法不适合太大的文件,因为byte数组不能太大
long skip(long n)//跳过几个字节不读
*******************FileOutputStream*****************
文件不存在时会自动新建
采用只有方法路径的构造方法会把原文件清空重写,需要谨慎使用!fos = new FileOutputStream("tempfile");
想要追加文件,可以在构造方法方法路径后面跟一个布尔类型的数据--true表示追加
fos = new FileOutputStream("tempfile",true);
String s = "我爱小马“;
byte[] bytes = s.getBytes();
fos.write(bytes);
*******************使用FileInputStream+FileOutputStream拷贝文件*****************
拷贝的过程应该是一边读一边写,什么文件都能拷贝
----------------------------------------------
//缓冲流专属
使用这些流时不需要自定义char、byte数组,自带缓冲
java.io.BufferReader
java.io.BufferWriter//构造方法只能传字符流,可以配合转换流使用
java.io.BuferedInputStream
java.io.BufferedOutputStream//构造方法只能传字节流,可以配合转换流使用
FileReader reader = new FileReader("CopyText");
BufferReader br = new BufferReader(reader);
//当一个流的构造方法需要传一个流的时候,这个被传进来的流叫做节点流
//外面负责包装的流,叫做包装流或处理流
//有一个特殊的方法 readLine() 读取一行但不带换行符
String s = null;
while((s = br.readLine()) != null)
{
System.out.println(s);
}
br.close();
//关闭最外层流,里面的节点流会自动关闭
-----------------------------------------------------------------------
//转换流(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
FileInputStream in = new FileInputStream("CopyTest");
InputStreamReader reader = new InputStreamReader(in);
BufferedReader br = new BufferedReader(reader);
-------------------------------------------------------------------------
//数据流专属(使用很少)
java.io.DataInputStream
java.io.DataOutputStream//将数据和数据类型一并写入文件,无法用记事本打开,相当于加密,想要读出来必须用DataInputStream,且要知道写入数据的类型和顺序
-----------------------------------------------------------------------------
//对象专属流
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
//标准输出流
java.io.PrintWriter
java.io.PrintStream(掌握)//标准字节输出流,默认输出到控制台
//联合起来写
System.out.println("");
//分开写
PrintStream ps = System.out;
ps.println("");
//标准输出流不需要手动关闭
内存和硬盘之间交互次数越少越好,效率越高
IDEA默认当前路径为工程Project的根
java.io.File类
File类和四大家族没有关系,不能完成文件的读和写,他是文件或目录路径名的抽象表达形式
File类中常用的方法:
参加序列化和反序列化的对象,必须实现sirializable接口,这个接口只是一个标志性接口,里面什么都没有,起到标志的作用,Java虚拟机看到这个接口,会为该类自动生成一个序列化版本号
//序列化
//创建Java对象
Student s = new Student(1111,"zhangsan");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("students"));
//序列化对象
oos.writeObject(s);
//刷新
oos.flush();
//关闭
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("students"));
Object obj = ois.readObject();
//反序列化得到一个对象,在这里是student,输出时会调用toString方法
System.out.println(obj);
ois.close();
//一次性序列化多个对象
//可以把对象放在集合当中,序列化集合
//如果不希望某一属性被序列化,可以用关键字transient修饰,表示游离的
//那么,序列化版本号是什么呢?序列化版本号是类的最初版本的指纹。所谓指纹就是表示类的字节码的唯一标识,它通常占用8个字节。Java使用 SHA算法计算类的指纹,SHA算法保证类文件发生任何形式的变化它的指纹也会发生改变。通常JVM在加载类文件形成字节码时会计算类的指纹,并在实施序列化操作时,将类的指纹写入到序列化文件中,具体的位置是放置在类描述符之后。在之后的反序列化过程中,JVM根据序列化文件中的类描述符加载运行环境下的类文件并再次计算类的指纹;如果类的指纹与序列化文件中的指纹相同,则反序列化成功;否则反序列化就会失败。如果在实施对象的序列化操作之后,产生对象的类文件发生了变化,而又希望反序列化时能够成功,就需要明确的在类文件中设置版本号。这样,由于变化所形成的类文件的后续版本实际上使用的还是最初的指纹。由于显示的在类文件中定义了序列化版本号,JVM在加载类时就不会计算新的指纹而是使用最初的指纹,因此就和序列化文件中保存的指纹相同,从而就不会导致反序列化的失败。
拷贝目录
IO和properties联合使用
以后经常改变的数据,可以单独写道一个文件中,使用程序动态读取,将来只需要修改这个文件的内容,Java代码不需要改动,不需要重新编译,服务器也不需要重启。
类似于以上机制的这种文件被称为配置文件,并且配置文件中的内容格式为:
key1 = value1
key2 = value2
配置文件建议以.properties结尾,但这不是必须的。
在属性配置文件中,井号(#)是注释,等号两边不要有空格
//IO和properties联合使用
//现在有一个文件,以key = value的形式存储,想要把文件中的内容存到properties集合中
FileReader reader = new Filereader("文件路径“);
Properties pro = new Properties();
pro,load(reader);//这个方法很重要,等号左边作为key,等号右边作为value
线程
1、进程是一个应用程序,线程是一个进程中的执行场景。一个进程可以启动多个线程。
2、进程A和进程B的内存独立不共享
线程A和线程B堆内存和方法区内存共享,一个线程一个栈,即多线程并发
3、main方法结束,只是主线程结束了,可能程序还不会结束(有其他线程)
4、对于单核的CPU来说,真的可以做到多线程并发吗
4核CPU表示在同一时间上,可以并发执行4个进程。
单核CPU只有一个大脑,不能做到真正的多线程并发,但是CPU处理速度极快,可以在多个线程之间切换,所以给人一种多线程并发的错觉。
5.实现多线程的第一种方式:编写一个类,继承java.lang.Thread(extends),重写run方法,run方法中的程序在另一个线程中执行。
start()方法调用后瞬间结束,线程启动成功,启动成功的线程会自动调用run方法,开始压栈。
如果直接调用run方法,mythread.run()则会直接执行方法,不会开启另一个线程
6、实现多线程的第二种方式:编写一个类,实现java,lang.Runnable接口,实现run方法
注意:run方法中的异常不能throws,因为子类不能比父类抛出更多异常
--使用的更多,面向接口
1、线程的生命周期
2、设置线程名字:线程对象.setName(" ")
获得线程名字:线程对象.getName()
3、获取当前线程对象:static Thread currentThread() //静态方法
4、sleep()//静态方法,参数是毫秒,让当前线程进入休眠状态,放弃CPU占有时间让其他线程占用
怎么唤醒一个正在睡眠的线程?
interrupt()方法--让sleep方法抛异常,然后catch语句执行完,接着往下执行代码
5、终止一个线程,在线程中设置一个布尔作为标志
关于多线程并发环境下,数据的安全问题*****非常重要
1、什么时候数据在多线程并发的环境下会存在安全问题呢?
--多线程并发
--有数据共享
--共享数据有修改的行为
2、如何解决线程安全问题?
--线程排队,不能并发,这种机制被称为线程同步机制。这样会牺牲一部分效率,但无法避免。
3、同步编程模型:一个线程执行必须等待另一个线程执行结束,两个线程之间有等待关系。
异步编程模型:线程各自执行各自的,谁也不用等谁。又叫多线程并发,效率较高。
异步就是并发,同步就是排队。
4、线程同步机制的语法:
synchronized(){
}
括号中传递的参数相当关键,必须是多线程共享的数据,才能达到多线程排队
()中写什么决定于你想让哪些线程同步,假设现在有t1、t2、t3三个线程,而只希望t1、t2排队,那么()中要写一个t1、t2共享的数据,而这个数据t3不共享
原理 :
当在运行状态中遇到synchronize关键字的时候,会进入锁池(一种阻塞状态),放弃所抢占的cpu时间,在这里找共享状态的对象锁,若找到了则进入就绪状态,继续抢夺cpu时间片
5、局部变量存储在栈中,所以不会存在线程安全问题。
6、synchronized出现在实例方法上,一定锁的是this,所以这种方式不灵活。另外,synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低,所以这种方式不常用。
7、synchronized出现在静态方法上,表示类锁 ,无论创建多少个对象,类锁只有一把。类锁通常用来保障静态变量的安全
8、线程死锁-导致程序一直僵持在那里
synchronized最好不要嵌套使用,很容易导致死锁
开发中应该怎么解决线程安全问题?
我们不会一上来就使用synchronized,这样会让程序的执行效率降低,用户体验不好,在不得已的情况下再选择。
第一种方案:尽量使用局部变量代替实例变量和静态变量
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象。
第三种方案:synchronized
1、Java语言中线程分为两大类:用户线程、守护线程(后台线程)--其中具有代表性的是垃圾回收线程
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
设置守护线程:线程.setDaemon(true)
2、定时器
间隔特定的时间,执行特定的程序。
目前使用最多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
3、实现线程的第三种方式:实现Callable接口(JDK8),这种方式实现的线程可以获取线程的返回值
4、Object类中的wait和notify方法(生产者和消费者模式)
第一:wait()
Object o = new Object();
o.wait();
//表示让正在o对象上活动的线程进入无期限等待状态
第二:notify()
o.notify()可以让在o对象上活动的线程被唤醒
反射机制
1、反射机制有什么用?
通过反射机制可以操作字节码文件,有点类似于黑客
2、要操作一个类的字节码,首先要获取这个类的字节码
第一种方法:Class c1 = Class.forName("java.lang.String");
//forName()为静态方法;参数为完整类名,包名不能省略;c1代表String.class文件,或者说代表String类型
//forName()的执行会导致类加载
第二种方法:任何一个对象都有getClass()方法
String s = "abc";
Class x = s.getClass();
第三种方法:任何一个类型,都有.class属性
Class x = String.class;