1.1 static关键字(解决对象之间相互冗余的属性和方法)
在前面面向对象的学习中,知道一个类包含成员变量和成员方法,比如定义一个学生类,其具有的成员变量有 姓名,年龄,成员方法有学习等
在一个类中,我们通常用static关键字来区分成员变量和成员方法对于每一个实例对象的归属问题。想上面所述,可能定义的学生类具有像校名、班级名等成员变量,不像姓名年龄这种,这对于同一个学校、同一个班级学生的学生对象来说是一模一样的,像这种对象一模一样的属性或行为我们用static来修饰,该成员变量或成员方法属于该类,并不会像其他成员变量一样在程序运行时会每个对象复制一份到堆内存,而它只会保留在方法区的静态区中,在内存中只有这一份。
1.1.1 static修饰成员变量
格式:static 数据类型 变量名
调用:类名.成员变量名 (因为数据类,不属于对象,因此用类名来调)
public class Demo {
public static void main(String[]args){
Student.schoolName="合肥工业大学";
Student s1 =new Student();
Student s2=new Student();
System.out.println(Student.schoolName);
System.out.println(Student.schoolName);
}
}
class Student{
String name;
int age;
static String schoolName;//static 修饰成员变量
//get/set方法;
//成员方法;
}
1.1.2 static 修饰成员方法
格式:修饰符 static 返回值类型 方法名(参数列表){}
调用:类名调用,类名.方法名
同一类中,静态方法只能使用静态内容(静态成员变量和静态成员方法),静态方法中出线非静态成员变量或者成员方法会报错。
public static void main(String[]args){
Student.setSchoolName("合肥工业大学");
Student s1 = new Student("tom",24);
Student s2 = new Student("jerry",23);
System.out.println(s1.getName()+","+s1.getAge()+","+Student.getSchoolName());
System.out.println(s2.getName()+","+s2.getAge()+","+Student.getSchoolName());
s1.eat();
Student.study();
}
}
class Student{
private String name;
private int age;
private static String schoolName;//static 修饰成员变量
//get/set方法;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getSchoolName() {
return schoolName;
}
public static void setSchoolName(String schoolName) {
Student.schoolName = schoolName;
}
//成员方法;
public void eat(){
System.out.println(getName()+" "+getAge()+" "+getSchoolName());
}
public static void study(){
System.out.println(getSchoolName());
}
1.2 继承(解决多个类之间相互冗余的成员变量和方法)
1.2.1 继承概念,格式
概念:在Java语言中,由于多个类中的成员变量和成员方法相同,会造成代码的冗余,我们可以将这些相同属性和行为抽取出来,单独放到一个类中,其他类无需再定义这些成员变量和方法,只需继承那一个类即可,这个类就是父类。
继承格式:关键词 extends
父类:修饰词 class 父类名{
}
子类:修饰词 class 子类名 extends 父类名{
}
继承可以提高代码的复用性,并且使类与类之间产生联系。
public class Demo {
public static void main(String[]args){
Student s=new Student("张三",18,80.5);
s.eat();
s.study();
Teacher t=new Teacher("李老师",35,4000.0);
t.eat();
t.teaching();
}
}
//父类
public class Person {
//要想被子类继承这里就不用private来修饰成员变量和成员方法
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+","+age+","+"在吃饭");
}
}
//子类
public class Teacher extends Person {
private double salary;
public Teacher() {
}
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void teaching(){
System.out.println(name+","+age+","+"上课工资为:"+salary);
}
}
//子类
public class Student extends Person {
private double score;
public Student() {
}
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public void study(){
System.out.println(name+","+age+","+"考试分数为"+score);
}
}
1.2.2 继承的内容
并不是父类中所有内容都会继承到子类,父类中的构造方法不会被子类继承(构造方法的方法名要和类名一致),还有父类中被private修饰的成员变量不能直接被子类访问,要通过提供get/set方法来访问。
public class Demo {
public static void main(String[]args){
Student s=new Student("张三",18,80.5);
s.eat();
// s.smokable;父类私有的方法不能被调用
s.study();
Teacher t=new Teacher("李老师",35,4000.0);
t.eat();
//t.smokable;父类私有的方法不能被调用
t.teaching();
}
}
public class Person {
//这里用private来修饰父类成员变量,要想成员变量能够访问这些成员变量,在下面就要提供get/set方法
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println(getName()+","+getAge()+","+"在吃饭");
}
//在这里用private来修饰成员方法
private void smokable(){
System.out.println(getName()+","+getAge()+",在抽烟");
}
}
public class Teacher extends Person {
private double salary;
public Teacher() {
}
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public void teaching(){
System.out.println(getName()+","+getAge()+","+"上课工资为:"+salary);
}
}
public class Student extends Person {
private double score;
public Student() {
}
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public void study(){
System.out.println(getName()+","+getAge()+","+"考试分数为"+score);
}
}
1.2.3 继承后的特点
成员变量:
当子类父类成员变量不重名时,这时访问是不受影响的,都能够正确访问;
当子类父类的成员变量重名时,这时访问是有影响的,访问父类:super.父类成员变量名
访问子类:this.子类成员变量名。super表示对父类对象的引用,this表示对当前类对象的引用。
成员方法:
当子类父类成员方法不重名时,这时访问是不受影响的,都能够正确访问;
当子类父类成员方法重名时,这时访问是收到影响的,对象在调用方法的时候,优先会在子类中查找对应的方法,若子类中存在,就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。
构造器: 构造器的名字要和类名一致,因此,子类是无法继承父类构造方法的。
构造器的作用是初始化对象成员变量数据的,必须先执行父类的初始化动作,在程序中,子类所有构造器的第一行都会自动调用父类的无参构造方法super();
1.2.4 方法的重写
当子类出现与父类一模一样的方法时(返回值类型,方法名,和参数列表都相同),会出现覆盖效果,也称为重写或者复写。
方法重写注意事项:
注意与方法重载的区别(重载在于方法名相同,参数的不同,详见基础笔记(二))
方法重写是发生在子父类之间的关系;
子类方法覆盖父类方法,必须保证其权限大于父类等于父类权限
子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
1.2.5 super和this的 用法
this.成员变量:调用本类成员变量。
super.成员变量:调用父类成员变量。
this.成员方法名(): 调用本类成员方法。
super.成员方法名(): 调用父类成员方法。
super(……):调用父类的构造器,根据参数匹配确认是无参还是有参构造。
this(……):调用本类的其他构造器,根据参数匹配确认。
注意:super(……)和this(……)都必须是在构造方法的第一行,所以不能同时出现。
1.2.6 继承的特点
Java的类只支持单继承,不支持多继承,一个类只能拥有一个父类。
一个类可以拥有多个子类。
可以多层继承。
1.2.7 引用数据类型作为方法参数和返回值以及成员变量
引用数据类型:数组、类、接口(下面以类为例)
引用数据类型作为方法参数时,数据类型是类名,变量是该类的一个对象。
引用数据类型作为返回值时,返回值类型是类名,返回值是这个类的对象。
引用数据类型作为成员变量时,传入这个类的对象即可。
1.3 抽象类
1.3.1 抽象类引入
父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有意义,而方法主体则没有存在的意义了。那么父类就完全只需要提供一个没有方法体的方法签名即可,具体实现交给子类自己去实现。我们定义没有方法体的方法称为抽象方法,包含抽象方法的类就是抽象类。
1.3.2 抽象类与抽象方法的定义格式(abstract)
抽象类:修饰词 abstract class 类名{
}
抽象方法:修饰词 abstract 返回值类型 方法名 (参数列表) ;
注意事项:抽象类不一定含有抽象方法,但是含有抽象方法的类一定为抽象类;
抽象方法没有方法体;
抽象类存在的意义就是为了被子类继承;子类必须重写父类所有的抽象方法,不然子类也要被定义成抽象类;
抽象类中可以有构造器,是供子类创建对象时初始化父类成员使用的,抽象类本身不能创建对象只能创建非抽象类的子类。
public class Demo {
public static void main(String[]args) {
Student s =new Student();
s.eat();
Teacher t =new Teacher();
t.eat();
}
}
//定义一个抽象类
public abstract class Person {
//该抽象类里的抽象方法
public abstract void eat();
}
//定义一个子类继承上面抽象类
public class Student extends Person {
//重写父类中的抽象方法
@Override
public void eat() {
System.out.println("学生在吃米饭");
}
}
//定义另一个子类继承上面抽象类
public class Teacher extends Person {
//重写父类中的抽象方法
@Override
public void eat() {
System.out.println("老师在吃面条");
}
}
1.3.3 抽象类的应用——模板设计模式
我们把固定的流程写入父类中,不同的地方就写成抽象方法,让不同的子类去实现。模板设计的优势:模板已经定义了通用架构,使用者只需要关心自己需要实现的功能。
public class Demo {
public static void main(String[]args) {
NewDriver nd= new NewDriver();
nd.driver();
System.out.println("************");
OldDriver od=new OldDriver();
od.driver();
}
}
//定义一个抽象类
public abstract class Driver {
//新老司机开车流程一样,有很多共同点
public void driver(){
System.out.println("开门");
System.out.println("点火");
driving();//新司机开车方式和老司机开车方式不一样,定义抽象方法
System.out.println("熄火");
System.out.println("下车");
}
public abstract void driving();
}
public class NewDriver extends Driver {
//继承抽象类,重写父类抽象方法
@Override
public void driving() {
System.out.println("新司机双手紧握方向盘!");
}
}
public class OldDriver extends Driver {
//继承抽象类,重写父类抽象方法
@Override
public void driving() {
System.out.println("老司机边开车边喝酒");
}
}
1.4 接口(interface)
1.4.1 接口的定义和使用
抽象类中可以有成员变量,构造方法和普通方法。而接口中是更加彻底的抽象,在接口中只含有抽象方法,没有构造方法之类的,因此接口同样不能创建对象。(JDK8以后接口中还有default修饰的静态方法和static修饰的默认方法)。
接口的定义格式:
修饰符 interface 接口名{
抽象方法;
}
类与接口的关系为实现关系,即类实习那接口,该类可以称为接口的实现类或者接口的子类。类实现接口的格式为:关键字(implements)
修饰符 class 类名 implements 接口名{
}
其中,实现类必须重写实现全部接口中的所有抽象方法,如果一个类实现了接口,但是没有完全重写实现所有接口中的所有抽象方法,这个类也必须定义为抽象类(强制性规范)
1.4.2 接口中成员方法和成员变量特点
接口中的方法会自动添加 public abstract
接口中成员变量默认会添加 public static final ;换句话说,接口中的定义的成员变量实际上是一个常量 常量必须要给初始值,调用时,由于是static修饰,因此,直接用接口名调用。
public class Student implements Person{
@Override
public void eat() {
System.out.println(Person.name+"边吃饭边学习");
}
}
/*interface Person {
void eata();
}*/
public interface Person {
int a=5;
// String name;成员变量相当于常量,需要初始化赋值
String name="tom";//实际上是public static final String name="zzx"
void eat();//省略了public abstract
}
1.4.3 接口的多实现与多继承
Java中的类只能单继承,一个子类只能继承一个父类,但是Java中的子类可以实现多个接口,这就是接口的多实现。如果实现了多接口,多个接口中存在重名的静态方法不会造成冲突,他们根据各自的接口名来访问静态方法。
public class 类名 implements 接口1,接口2,接口3……{
}
Java中的类只能单继承,一个子类只能继承一个父类,但是Java中的接口可以多继承,即一个接口可以继承多个接口,接口与接口间是继承关系,而且接口间的继承实际上是把其它接口的抽象方法与本接口的抽象方法进行合并。
public interface 接口1 extends 接口2,接口3,……{
}
1.4.4 继承与实现的优先级
当一个类,既继承了一个父类,有实现了若干接口时,父类的成员方法与接口中的默认方法重名,子类会根据就近原则来执行父类的成员方法。
1.5 final关键字
1.5.1 final修饰类和方法
学习继承之后,我们可以通过子类来重写父类的内容,为了避免这种随意修改,Java中用final用来修饰不可改变的内容。
final修饰的类不可被继承。
final修饰的方法不可被重写。
1.5.2 final修饰变量
final修饰局部变量只能被赋值一次;
final修饰成员变量:由于成员变量设计初始化问题,我们可以通过定义初始化和构造器初始化两种方法任选其一。
定义初始化:在成员变量定义的时候就赋值。
构造器初始化:在每个构造器(包括无参构造与有参构造)里都需要且仅需要赋值一次。
1.6 代码块
构造代码块/实例代码块:{}
静态代码块:static{}
静态代码块和实例代码块都是放在类中方法外(成员位置)。
每次执行构造方法时,都先执行构造代码块;静态代码块只在类加载的时候执行一次,主要可以用来给静态成员赋值,和数据库驱动加载。
1.7 单例设计模式
正常情况下,一个类可以创建多个对象造成内存的浪费,但是,有些时候我们只想一个类只创建一个对象来节省内存。因此单例设计模式的作用是使该类在程序运行过程中只能创建一个对象从而节省内存。
单例设计模式分为饿汉式单例模式和懒汉式单例模式。饿汉式单例模式是创建对象很早,而懒汉式单例模式则是创建对象很晚,直到使用时才创建。
单例模式设计步骤:
1.构造方法私有
2.自己创建对象并保存
3.提供公共访问方式,返回这个单例对象
public class Demo {
public static void main(String[]args) {
AudioPlayer ap1= AudioPlayer.getInstance();
AudioPlayer ap2=AudioPlayer.getInstance();
System.out.println(ap1 == ap2);//判断两个对象是否是同一个
VedioPlayer vp1=VedioPlayer.getInstance();
VedioPlayer vp2= VedioPlayer.getInstance();
System.out.println(vp1 == vp2);//判断两个对象是否是同一个
}
}
//饿汉式单例设计模式
public class AudioPlayer {
//构造方法私有
private AudioPlayer(){
}
//创建对象并通过成员变量保存
//要用static修饰不然公共方法无法调用
//private和final用来严格控制只能创建唯一一个对象
private static final AudioPlayer ap = new AudioPlayer();
//提供公共静态访问方式并返回对象,(在测试类里不可能通过对象.方法名,因此是静态方法)
public static AudioPlayer getInstance(){
return ap;
}
}
//懒汉式单例设计模式
public class VedioPlayer {
//构造方法私有
private VedioPlayer(){
}
//自己创建静态对象成员变量后面用来保存创建的对象
private static VedioPlayer vp;
//提供公共访问方法,判断对象是否创建,若没创建则创建对象并给成员变量保存,最后返回
public static VedioPlayer getInstance(){
if (vp==null){
vp=new VedioPlayer();
}
return vp;
}
}
1.8 枚举
枚举的概念:枚举是一种特别的类,它具有固定实例个数
枚举格式:enum 枚举类名{
枚举项1,枚举项2,枚举项3……
}
枚举使用:枚举类名.枚举项。
注意:枚举底层也是一个类,可以具有成员变量,成员方法,和构造方法。
枚举的好处:只能传入枚举种指定的值,保证了数据的合法性。
1.9 多态
1.9.1多态的定义、前提、优点
面向对象具有三大特性:封装、继承、多态
多态是指同一行为具有多个不同的表示形式。(在测试类定义一个方法,传入父类类型的对象或变量,可以根据不同子类对象的调用,从而该方法会有不同的表现形式)
多态的前提:
存在继承和实现接口;
方法的重写;
父类引用指向子类对象;
多态的好处:减少程序冗余,并且具有良好的拓展性。
1.9.2 多态的格式
父类 变量名 = new 子类();
接口 变量名 = new 实现类();
变量名.方法名;
当使用多态调用方法时,首先会检查该父类种是否存在该方法,如果不存在,则会报错;如果父类中存在该方法,那么执行的是子类重写后的方法,如果子类没有重写该方法,则会执行父类的该方法。
public class Demo01 {
public static void main(String[] args) {
//多态的格式
Animal a1=new Dog();
// test(a1);
test(a1);
Animal a2=new Cat();
//test(a2);
test(a2);
}
//多态优点的体现
public static void test(Animal animal){
animal.eat();
}
//普通方式由于传入参数不同,要写两个方法。
/*public static void test(Dog dog){
dog.eat();
} public static void test(Cat cat){
cat.eat();
}*/
}
//Animal父类
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
//Dog子类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗在吃骨头");
}
}
//Cat子类
public class Cat extends Animal {
//示例不重写父类方法
}
1.9.3 引用类型转换
回顾基本数据类型转换:
自动类型转换: 范围小的赋值给范围大的.自动完成:double d = 5
强制类型转换: 范围大的赋值给范围小的,强制转换:int i = (int)3.14
在基本数据类型进行运算的时候,有一个自动类型提升
引用数据类型转换:在多态中,由于不能调用子类独有父类没有的方法,会导致编译错误,在调用子类特有的方法时,必须做向下转型。
向上转型:多态本身就是子类类型向父类类型转换的过程,这个过程是默认的
父类类型 变量=new 子类类型();
Animal a1= new Dog();
向下转型:父类类型向子类类型转换的过程。这是为了调用子类特有的方法。
子类类型 变量 = (子类类型)父类对象
Dog d=(Dog) a1;
public class Demo {
public static void main(String[]args) {
//多态的格式就是向上转型
Animal a1=new Dog();
a1.eat();
// a1.lookHome(); 不能使用子类特有方法
//向下转型
Dog d=(Dog)a1;
//调用特有方法
d.lookHome();
//多态的格式就是向上转型
Animal a2=new Cat();
a2.eat();
//a2.catchMouse;不能调用子类特有方法
Cat c=(Cat)a2;
c.catchMouse();
}
}
//父类
public abstract class Animal {
public abstract void eat();
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
//子类特有的方法
public void lookHome(){
System.out.println("狗看家");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse(){
System.out.println("猫抓老鼠");
}
}
1.9.4 instanceof 关键字
在我们做向下转型前,需要对对象类型做出判断。如果对象是指定类型,返回true,否则返回false;
instanceof 关键字格式:对象名 instanceof 类名
public class Demo {
public static void main(String[]args) {
//a1是Animal类型的变量,是Dog子类的实例
Animal a1=new Dog();
a1.eat();
//判断a1对象是属于哪一个子类的实例
if(a1 instanceof Dog){
Dog d=(Dog)a1;
d.lookHome();
}else if (a1 instanceof Cat){
Cat c=(Cat)a1;
c.catchMouse();
}
/* a1.eat();
// a1.lookHome(); 不能使用子类特有方法
//向下转型
Dog d=(Dog)a1;
//调用特有方法
d.lookHome();
Animal a2=new Cat();
a2.eat();
// a2.catchMouse;不能调用子类特有方法
Cat c=(Cat)a2;
c.catchMouse();*/
}
}
1.10 内部类
1.10.1 内部类的概念
我们将一个类A定义在类B里面,A称为内部类,B称为外部类。内部类是java类的五大成份之一。
1.10.2 内部类的分类
我们根据在类中的位置不同可以分为:静态内部类、实例内部类、局部内部类和匿名内部类
静态内部类:位置类中方法外,用static修饰,静态内部类可以直接访问外部类的静态成员,属于外部类
使用格式:外部类.内部类
静态内部类创建对象格式:外部类.内部类 变量 = new 外部类.内部类构造器
实例内部类:位置类中方法外,没有static修饰,属于外部类对象
使用格式:外部类.内部类
实例内部类创建对象格式:先创建外部类对象,通过外部类对象来创建内部类对象。Body b=new Body(); Body.Heart h=b.new Heart();
局部内部类:定义在方法中的类,局部内部类编译后仍然是一个独立的类,编译后文件名还有一个"$"和一个数字。
匿名内部类(重点):匿名内部类的本质是一个带具体实现的父类或者父接口匿名的子类对象,他只能使用一次。
匿名内部类格式:
new 父类/父接口名(){
//方法重写
};
匿名内部类的特点: 定义一个没有名字的内部类
这个类实现了父类,或者父类接口
匿名内部类会创建这个没有名字的类的对象
匿名内部类的使用场景:在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。
1.11权限修饰符
1.11.1 四种权限修饰符
public: 公共的,在所有地方都能使用
protected:当前类,当前包,当前类的子类都可以访问
缺省(没有修饰符):当前类,当前包可以使用
private:私有的,当前类可以访问
一般情况下:我们这样使用权限
成员变量使用private,隐藏细节;
构造方法使用public,方便创建对象;
成员方法使用public,方便调用方法。
1.12 Object类
Object类时java语言的根类,即所有类的父类。
1.12.1 toString方法
toString方法是返回该对象的字符串表示,在实际开发中,自定义的类若是没有重写toString方法则返回的是内存地址,若是想要得到对象的具体内容,我们需要重写toString方法。
1.12.2 equals方法
在Object类中equals方法是用来比较对象的地址是否一致,和“==”效果一致,地址值一致就返回true,地址值不一致则返回flase;但是在实际开发过程中我们不仅要比较地址,往往更加需要要对对象的内容进行比较。这时候就要重写equals方法。
1.12.3 Objects类
Objects类是对象工具类,它里面的方法都是来操作对象的
我们重写的equals方法很容易抛出空指针异常,而Objects类中的equals方法优化了这个问题
public static boolean equals(Object a,Object b){
}
在Objects类中isNull方法用来判断对象是否为null,如果null返回true。
static boolean isNull(Object obj)
//调用方法:
Object.isNull(对象);
1.13 日期
1.13.1 Date类
Date类表示特定的瞬间,精确到毫秒
//通过Date的两个构造方法来创建对象
Date date=new Date();//获取当前日期时间
Date date=new Date(long类型数据);//获取从1970年1月1日00:00:00到指定时间的毫秒值的日期时间
Date类的常用方法:
public long getTime();//获取当前时间距离1970年1月1日00:00:00的毫秒值。
public void setTime(long time);//修改日期时间,在1970年1月1日00:00:00的基础上加上指定毫秒值,得到的日期时间
1.13.2 DateFormat类
DateFormat类是一个抽象类不能直接使用,我们通过他的子类SimpleDateFormat的有参构造来创建对象。
该类的作用对时间进行格式化,时间字符串和Date类对象相互转换。
Date类对象转换成时间字符串:format()方法
Date date =new Date();
SimpleDateFormat sdf=new SimpleDateFormat("指定时间字符串格式");
String dateString =sdf.format(date);
时间字符串转换成Date类:parse()方法
SimpleDateFormat sdf =SimpleDateFormat("指定时间字符串格式");
String dateString ="输入字符串格式";// 上述指定字符串格式要和输入字符串格式一致,不然会出现异常。
Date date=sdf.parse(dateString);
1.13.3 Calendar类
Calendar类表示一个日历类,它是一个抽象类,不能创建对象,我们用它的子类GregorianCalendar类来获取对象:
1.直接获取GregorianCalendar对象;
2.通过Calendar类的静态方法getInstance()方法来获取GregorianCalendar对象。
Calendar类的常用方法:
public int get(int field); 获取某个字段的值,其中int field=Calendar.YEAR等;
public void set(int field,int value); 设置某个字段的值;
public void add(int field,int amount); 为某个字段增加/减少指定的值。
public class Demo{
public static void main(String[]args){
Calendar cal=Calendar.getInstance();
int year=cal.get(Calendar.YEAR);
int month=cal.get(Calendar.MONTH);
cal.set(Calendar.YEAR,2019);
cal.add(Calendar.YEAR,10);
}
}
1.14 Math类
Math类构造方法私有了,不允许我们再创造对象,由于Math类中的方法都是静态方法,因此,我们可以通过内名直接调用Math类的常用方法。
public static int abs(int i);获取绝对值
public static double pow(double a,double b);a的b次方
public static double ceil(double a);向上取整
public static double floor(double a);向下取整
public static long round(double a);四舍五入取整
1.15 System类
System的构造方法私有了,不能创建对象,但是System类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作。我们可以通过System.方法名来调用
public static void exit(int status);//终止当前运行的java虚拟机,非零表示异常终止
public static long currentTimeMillis();//返回当前时间值,以毫秒为单位,可以用来计算程序运行时间
1.16BigDecimal类
表示一个精确十进制数字,保证小数是绝对准确的,本身不会变化
构造方法:BigDecimal(double val) 使用double来创建BigDecimal
BigDecimal(String val) 使用字符串来创建BigDecimal (使用该方法比较准确)
常用方法:BigDecimal add(BigDecimal augend) 加法
BigDecimal subtract(BigDecimal substrahend) 减法
BigDecimal multiply(BigDecimal multiplicand) 乘法
BigDecimal divide(BigDecimal divisor) 除法
注意,除法在除不尽的情况下,使用方法:
BigDecimal divide (BigDecimal divisor,int scale,RoundingMode roundingMole)
int scale:指定保留位数
RoundingMole roundingMole:舍弃模式,例如:HALF_UP
1.17包装类
基本数据类型效率高,但是功能有限,只能做加减乘除运算
为了对基本数据类型进行更多的操作,Java为每种基本数据类型提供了对应的包装类。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
基本数据类型转换成包装类(自动装箱):包装类类名 对象=包装类类名.valueOf(基本数据类型);
例如 Integer i=Integer.valueOf(10);
包装类转换成基本数据类型(自动拆箱):基本数据类型 变量=包装类对象.基本数据类型Value();
例如 double d=i.doubleValue();
基本数据类型转换成字符串:String s=String.valueOf(10);
字符串转换成基本数据类型: parse()方法
int i =Integer.parseInt(“10”)
格式:基本数据类型 变量名=基本数据类型对应的包装类.parse基本数据类型(字符串)
1.18 泛型
泛型:指可以在类或者方法中提前地使用未知的类型。参数化类型,让类型可以像变量一样变化。泛型在定义的时候不具体,使用的时候才变得具体
泛型的好处:避免了类型强转的麻烦。
泛型定义的格式:
含泛型的类:修饰符 class 类名<代表泛型的变量>{ } 可以在创建对象的时候确定泛型
含泛型的方法:修饰符<代表泛型的变量> 返回值类型 方法名(参数){} 可以在调用方法时,确定泛型的类型
含泛型的接口:修饰符 interface 接口名<代表泛型的变量>{} 可以在子类实现接口时就可以确定泛型的类型;也可以在实现类创建对象时确定泛型的类型。
泛型通配符:“?”表示未知通配符,不知道使用什么类型来接受时,就可以考虑泛型通配符。
泛型通配符的高级使用:设置上下限
设置上限:类型名称 <? extends 类> 对象名称
设置下限:类型名称<? super 类> 对象名称
1.19 Collection集合(它是单列集合)
集合:集合时Java中提供的一种容器,可以用来存储多个数据。
集合与数组的区别:数组的长度是固定的,集合的长度是可变的
数组中存储的是同一类型的元素,可以存储任意类型数据,集合存储的都是引用数据类型,如果想存储基本类型数据需要存储对应的包装类型。
Collection集合中的常用方法:
public boolean add(E,e); 把给定的元素添加到当前集合中
public boolean addAll(list/set,E e1,e2,e3……); 把给定的所有元素添加到当前集合中
public boolean remove(E,e);把给定的元素在当前集合中删除
public boolean contains(Object obj);判断当前集合中是否包含给定的元素
public boolean isEmpty();判断当前集合是否为空
public int size(); 返回集合中元素的个数
public Object[] toArray();把集合中的元素存储到数组中
1.20 Iterator迭代器
在上述Collection集合继承图中,list集合的特点是有索引,元素输出有序,且元素可重复,但是set集合的特点是没有索引,元素输出无序(LinkedHashSet有序但是不常用),且元素不可重复。list集合我们可以通过索引进行for循环来遍历,但是set集合没有索引,我们怎么来遍历呢?Collection提供了Iterator迭代器来遍历list/set集合。
Iterator迭代器的概念:获取Collection集合中的元素,先判断集合中有没有元素,如果有,就把这个元素取出来。再接着判断,如果还有就再取出来。一直把集合中的所有元素全部取出来。这个过程就称为迭代。
Collection集合接口中的Iterator方法:Iterator<> iterator(); 返回值类型Iterator<>,方法名iterator
使用Iterator迭代器的三个步骤:调用方法 Iterator<> iterator=集合名.iterator();
判断是否有下一个元素:iterator.hasNext();
获取迭代元素:数据类型 变量名=iterator.next();
ArrayList<String>list =new ArrayList<>();
Iterator<String>iterator =list.iterator;
while(list.hasNext()){
String s=list.next();
System.out.println(s);
}
Iterator注意事项:在迭代过程中,不能进行其他操作,Iterator迭代器存在并发修改异常。
增强for循环:集合名/数组名.for
数组的增强for循环底层是普通for循环;集合的增强for循环底层是迭代器;
增强for循环适用于只想取出数据,不关心索引的情况。
1.21 List集合
1.21.1 List集合的特点与常用方法
特点:有索引,元素可重复,存储和取出有顺序
List集合的常用方法:
void add(int index,E element); 将指定的元素插入此集合的指定位置
E get(int index); 返回此集合中指定位置的元素
E set(int index,E element); 用指定的元素来替代指定索引上的元素,并返回修改前的元素
E remove(int index); 删除该列表中指定位置的元素,并返回被移除的元素
1.21.2 ArrayList集合
ArrayList集合的底层结构是数组,ArrayList中有一个成员变量为 Object[] elementData, ArrayList就是靠这个数组来存储数据的,在创建该集合时,会给这个数组赋值{}空数组,在第一次添加数据时,会创建一个长度为10的数组,并且把数据放入其中。
ArrayList集合的特点:查询块,增删慢。
ArrayList<String>list=new ArrayList<>();
1.21.3 LinkedList集合
LinkedList集合的底层结构是双向链表,如果在实际开发中,我们需要经常对集合中的首尾元素进行操作,这时我们就需要用到LinkedList集合,因为LinkedList集合中提供了大量的首尾操作的方法:
public E getFirst();返回此集合的第一个元素
public E getLast();返回此集合的最后一个元素
public E removeFirst();移除并返回此集合的第一个元素
public E removeLast();移除并返回次集合的最后一个元素
public E pop();从此集合所表示的堆栈处弹出一个元素
public void push(E,e);将指定元素推入此集合所表示的堆栈
public boolean isEmpty();判断是否为空
LinkedList集合的特点:查询慢,增删快。
LinkedList<String>list=new LinkedList<>();
1.22 Set集合
1.22.1 Set集合的特点和常用方法
特点:无索引,元素不可重复,存储和取出无序(针对HashSet集合)
方法:与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的补充。
1.22.2 HashSet集合
HashSet底层其实是一个HashMap(哈希表),它是根据对象的哈希值来确定元素在集合中的存储位置。它具有良好的查找和增删功能,其中保证元素唯一性的方式是通过hashCode与equals方法。注意:当我们通过HashSet来存储自定义类型元素时,为了保证元素的唯一性,要重写HashCode与equals方法。
HashSet<String>set=new HashSet<>();
HashSet集合存储数据的结构:在JDK1.8之前是数组加链表来实现,即用数组来处理冲突,把同一哈希值的链表放在一个数组里。但是当一个桶里的hash值相等的元素太多了时,我们通过key值来查找元素的效率就很低。因此,在JDK1.8之后,HashSet存储数据的结构就变成了数组加链表加红黑树。当链表的长度大于8时,就会转成红黑树。
HashCode()方法返回对象的哈希码值,我们可以把哈希码值转换成16进制就变成对象的地址值。
哈希表存储元素的过程:如果HashCode值不相同,直接存储,
如果HashCode值相同,调用equals方法,如果equals不相同则存储,如果equals相同 则不存储。
1.22.3 LinkedHashSet
特点:无索引,元素不可重复,存储与取出有顺序
1.22.4 TreeSet
TreeSet集合是Set接口的一个实现类,底层结构为TreeMap,
TreeSet的特点:无索引,元素不可重复,输出元素时会自然排序好然后输出,也可通过提供的Comparator比较器进行排序。这个取决于使用的构造方法:
public TreeSet(); 会自然排序
public TreeSet(Comparator<E> comparator);指定升序或者降序
//例如:
TreeSet<Integer> treeSet2 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 前面 - 后面 是升序
// 后面 - 前面 是降序
return o2 - o1;
}
}); // ctrl + q: 看方法的参数
1.22.5 如何选择单列集合
首先看要求元素能不能重复,不能重复就选Set接口下的HashSet和TreeSet
如果能重复,再判断该集合应用要求是增删还是查询,若是查询比较多就用ArrayList,如果是要求增删比较多,那么就应用LinkedList。
1.22.6 可变参数
我们前面学过方法的重载,他是指同名方法参数列表不同(重载与修饰符返回值等无关)的方法。使用重载可以解决参数数量不相同的情况,但是有时还是很麻烦。在JDK1.5后Java提出了可变参数,即参数的个数可以发生变化。
可变参数的格式:
修饰符 返回值类型 方法名(数据类型…变量名){
}
可变参数的本质是一个数组
可变参数的优点:可以简化代码,避免写很多重载的方法
public class demo{
public static void main(String[]args){
test(1);
test(1,2);
test(1,2,3);
test(1,2,3,4);
}
public static int test(int...a){
int sum=0;
for(int i=0;i<a.length;i++){
System.out.print(a[i]+"\t");
sum+=a[i];
}
System.out.println("总和:"+sum);
return sum;
}
}
可变参数的注意事项:一个方法只能有一个可变参数
在参数列表中,可变参数要放在最后
1.23 Collections常用功能
Collections是用来操作Collection集合的工具类,常用方法有:
Collections.addAll(集合名,元素1,元素2……) 将后面所有的元素放入前面的集合中
Collections.shuffle(集合名) 将集合中元素的顺序随机打乱
Collections.sort(集合名) 将集合中的元素按照自然顺序进行排序
重点内容Comparator 比较器:
Collections.sort(集合名,new Comparator<>{}) 匿名内部类当参数传入方法,需要对Comparable进行重写。该方法指定升序或者降序来给集合元素排序。(注意自定义对象的比较排序:通过对象的某一属性来排序,return 对象.属性名-对象.属性名)
ArrayList<Integer>list=new ArrayList<>();
Collections.sort(list,new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
1.24 Map接口
现实生活中,对象与对象之间经常出现一种对应关系,例如IP地址与主机名,身份证号与人等,这种对应关系叫做映射。在Java中,我们通常用Map集合来存放具有这种映射关系的对象。在Map集合中,将键映射到值,以键值对的格式来存储元素,不能存在重复的键,每个键最多映射到一个值。可以多个键映射到同一个值。
Map集合的特点:元素是键值对形式;不能包含重复的键;一个键只能对应一个值
Map常用方法:put(key,value)添加或者修改,如果键不存在则是做添加操作,如果键存在就是做修改操作
get(key) 通过键来获取值
remove(key)通过键删除这对数据
size() 获取这个集合中键值对的数量
containsKey()与containsValue() 判断集合中是否具有某个键或者值
HashMap: Map集合中我们通常使用的是HashMap,下面以HashMap为例。
public class HashMapDemo{
public static void main (String[]args){
//创建HashMap集合
Map<String,String>map=new HashMap<>();
//常用方法
map.put("name","zhangliming");
map.put("age","18");
map.put("height","187");
//打印集合
System.out.println("map:"+map);
System.out,println(map.get(name));
System.out.println(map.remove(height));
System.out.println(map.size());
//Map集合的两种遍历方法 通过键找值;通过entry(键值对对象)来遍历
//一:keySet()方法
//获取键的单列集合
Set<String>keySet=map.keySet();
//增强for遍历
for(String key.keySet){
//通过键来找值
String value=map.get(key);
System.out.println(key+"="+value);
}
//二:entrySet()方法
//获得map集合的entry(键值对对象)的单列集合
Set<map.Entry<String,String>>entrySet=map.entrySet();
//遍历entrySet集合
for(map.Entry<String,String> entry.entrySet){
//entry.getKey()获得entry的键,entry.getValue()获得entry的值
String key=entry.getKey();
String value=entry.getValue();
System.out.println(key+"="+value);
}
}
}
用HashMap来存储自定义对象,注意判断信息内容相同的不同地址的对象能不能存进HashMap中,一般是将信息内容相同的不给存进去,因此要重写hashCode()方法和equals()方法
LinkedHashMap是HashMap的子类,应用较少,HashMap集合存储与取出是无序的,但是LinkedHashMap集合存储与取出是有序的。
TreeMap:会根据键的顺序进行排序
构造方法:TreeMap() 这种没有传入比较器的是在TreeMap集合创建的时候使用键的自然顺序排序。
TreeMap(new Comparator<>( ){ }) 这种在创建TreeMap集合的时候,使用匿名内部类,实现Comparator接口,重写方法,并创建了一个对象,最后集合会根据比较器的顺序来进行排序。
HashMap集合经典案例:输入一串字符串,来统计里面小写字母,大写字母,数字的个数
public class Demo{
public static void main(String[]args){
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个字符串:")
String line=sc.nextLine();
//创建一个HashMap集合,字符作为键,个数作为值
Map<Character,Integer>map=new HashMap<>();
//字符串底层其实是数组,我们来遍历输入的字符串
for(int i=0,i<line.length,i++){
//获取每一个字符
char c=line.charAt(i);
//判断集合里面是否包含了字符,如果包含了,数量加一,如果不包括,就把字符添加进集合,次数设置1
if(map.containsKey(c)){
int count=map.get(c);
map.put(c,count+1);
}else{
map.put(c,1);
}
}
//遍历HashMap集合
Set<map.Entry<String,Integer>>entrySet=map.entrySet();
for(map.Entry<String,Integer>entry.entrySet){
String key=entry.getKey();
Integer value=entry.getValue();
System.out.println(key+"="+value);
}
}
}
1.25 冒泡排序,选择排序与二分查找的简单实现
1.25.1 冒泡排序
冒泡排序是一种排序方式,对要进行排序的数据进行两两比较,把较大的放在后面,依次对所有的数据进行操作,直至所有数据按要求完成排序。
原理:相邻元素比较大的往后放,n个元素的数组要比较(n-1)轮,第x轮又要比较arr.length-1-(x-1) 次,因为每一轮都会确定一个最大的数,在接下来就不参与比较 遍历数组Arrays.toString(arr)
package com.itheima.demo;
import java.util.Arrays;
public class Demo{
public static void main(String[]args){
//定义一个数组用来排序
int []arr={1,7,4,5,3,2};
System.out.println(Arrays.toString(arr));
//总共需要比较arr.length-1 轮
for(int i=0;i<arr.length-1;i++){
//每轮需要比较arr.length-1-(i-1)次
for(int j=0;j<arr.length-1-i;j++){
//相邻两位比较 即arr[j]和arr[j+1]相比较
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
//遍历数组
System.out.println(Arrays.toString(arr));
}
}
1.25.2 选择排序
选择元素依次和后面的每个元素进行比较,把较小值放在前面,选择元素从第一个开始选择,直到所有元素排好序。
原理:小值放在前面,n个元素要比较n-1轮;第x轮比较arr.length-x次
package com.itheima.demo;
import java.util.Arrays;
public class Demo{
public static void main(String[]args){
int []arr={1,7,4,5,3,2};
//要比较arr.length-1轮
for(int i=0;i<arr.length-1;i++){
//arr[i]要依次和后面的每一个比较,因此(i+1)<=j<=(arr.length-1)
for(int j=1+i;j<arr.length;j++){
if(arr[i]>arr[j]){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
1.25.3 二分查找
普通查找:遍历数组,获取每一个元素,判断当前遍历的元素是否和目标元素相同。
二分查找的前提:适用于已经排好序的数组
二分查找:每次都去获取数组的中间索引所对应的元素,如果中间元素的值大于要查找的值,则说明要查找的值在左侧,如果中间元素的值小于要查找的值则说明要查找的值在右侧;然后缩小一半的范围,又重复上述步骤,每次少一半的范围。
//假设有一个给定有序数组(10,14,21,38,45,47,53,81,87,99),要查找50出现的索引
package com.itheima.demo;
public class Demo{
public static void main(String[]args){
//给定排好序的数组和要找的值
int []arr={10,14,21,38,45,47,53,81,87,99};
int number=50;
//定义方法
int index=binarySearch(arr,number);
System.out.println("索引为:"+index);
}
public static int binarySearch(int[]arr,int number){
//因为这里要缩小范围,因此我们要定义一组动态的索引
int left=0;
int right=arr.length;
while(left<=right){
//设置中间值
int midIndex=(left+right)/2;
int mid=arr[midIndex];
//判断中间值和要找的值的大小,来调整索引,并且是循环查找
if(number==mid){
return midIndex;
}else if(number>mid){
left=midIndex+1;
}else if(number<mid){
right=midIndex-1;
}
}
//没有找到就返回-1
return -1;
}
}