面向对象(OOP-object oriented programming)与面向过程(POP-procedure oriented progranmming)
举例:
//面向过程
1. 打开冰箱
2. 把大象装进冰箱
3. 把冰箱门关上//面向对象(强调具备了功能的对象,考虑谁来做)
人{打开(冰箱){冰箱.打开();}操作(大象){大象.进入冰箱();}关闭(冰箱){冰箱.关闭();}
} 冰箱{打开(){};关闭(){};
}大象{进入冰箱(){};
}
思想概述
类(class)和对象(object)是面向对象的核心概念
类=抽象概念的人;对象=实实在在的某个人
面向对象程序设计的重点是类的设计;类的设计,其实就是类的成员的设计
Java类及类的成员
常见的类的成员有:
类的成员构成:
public class Person {String name;int age = 1;boolean isMale;public void eat(){System.out.println("人可以吃饭");}public void sleep(){System.out.println("人可以睡觉");}public void talk(String language){System.out.println("人可以说"+language);}
}
创建类的对象:
public class testPerson {public static void main(String[] args) {Person p1 = new Person();//调用对象的属性和方法p1.name = "Tom";p1.age = 20;p1.isMale = true;//使用对象调用类的成员p1.eat();p1.sleep();String lang = "English";p1.talk(lang);}
}
如果创建了一个类的多个对象,则每个对象都独立地拥有一套类的属性(非static的),意味着如果我们修改一个对象的属性a,则不影响另一个对象的属性a
Person p2 = new Person();
System.out.println(p2.name);//null
类的访问机制
对象的内存解析:
JVM规定的内存结构
堆(Heap):此内存区的唯一目的就是存放对象的实例,所有的对象实例及数组都要在堆上分配
栈(Stack):指虚拟机栈(VM Stack),虚拟机栈用于存储局部变量等,方法执行完自动释放
方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
内存解析:
Person p1 = new Person();
p1.name = "Tom";
p1.isMale = true;Person p2 = new Person();Person p3 = p1;//p1的地址值赋给p3,没有声明一个p3对象
p3.age = 10;
匿名对象:
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象
new Person().eat();
使用情况
语法格式:
修饰符 数据类型 属性名 = 初始化值;
说明;
修饰符:
数据类型:
任何基本数据类型或引用型数据类型
属性名:
属于标识符,符合命名规则即可
变量的分类:
**成员变量:**声明在——方法体之外,类体{ } 内
**局部变量:**声明在——方法体内部、方法形参、代码块内、构造器形参、构造器内部
形参(方法、构造器中定义的变量)
public void talk(String language){ System.out.println("人可以说"+language);
}
方法局部变量(在方法内定义)
public void eat(){ String food = "烙饼"; System.out.println("人喜欢吃"+food);
}
代码块局部变量(在代码块中定义)
区别比较:
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内部、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以使用final |
初始化值 | 有默认初始化值 | 没有默认的初始化值,必须显式赋值。(形参在调用时赋值即可) |
内存加载的位置 | 堆空间 或 静态域内 | 栈空间 |
给属性赋值的方法:
对象.方法
或对象.属性
的方式赋值什么是方法?
方法声明格式:
修饰符 返回值类型 方法名 (参数类型 形参1,参数类型 形参2,...){方法体程序代码;return 返回值;
}
说明:
return关键字的使用
return 数据
方法返回所要的数据方法在使用中,可以调用当前类的属性或方法
方法中不可以定义方法
public class Person {String name;int age ;boolean isMale;public void eat(){System.out.println("人可以吃饭");}public void sleep(){eat(); //调用当前类方法if(age>0){ //调用当前类属性System.out.println("人可以睡觉");} }
}
练习1:
利用面向对象的编程方法,设计类Circle计算圆的面积
public class Person {double radius;public double findArea(){ //不适用形参,设计时保留类的属性return radius*radius*Math.PI;}
}public class testPerson {public static void main(String[] args) {Person p1 = new Person();p1.radius=2.4;System.out.println(p1.findArea());}
}
练习2:对象数组
定义类Student,包含三个属性:学号number(int),年级state(int),成绩
score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
public class Student {int number;int state;int score;@Overridepublic String toString() {return "Student{" + "number=" + number + ", state=" + state + ", score=" + score + '}';}
}public class TestStudent {public static void main(String[] args) {//声明一个Student的数组Student[] stus = new Student[20];for(int i=0;i< stus.length;++i){stus[i] = new Student();stus[i].number = i+1;stus[i].state = (int)(Math.random()*(6-1+1))+1;stus[i].score = (int)(Math.random()*(100+1));}TestStudent testStudent = new TestStudent();testStudent.findStudent(stus);testStudent.popSort(stus);}//遍历对象数组找3年级public void findStudent(Student[] stus){for(int i=0;i< stus.length;++i){if(stus[i].state == 3){System.out.println(stus[i].toString());}}}//按成绩冒泡排序public void popSort(Student[] stus){for(int i=0;i< stus.length;++i){for(int j=0;j< stus.length-1-i;++j){if(stus[j].score>stus[j+1].score){Student temp = stus[j];stus[j]=stus[j+1];stus[j+1]=temp;}}}}
}
方法的重载:
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可(为不同参数,提供不同的功能)
特点:与方法的返回值、权限修饰符 无关,只看参数列表
示例:
public class PrintStream {public static void print(int i) {……}public static void print(float f) {……}public static void print(String s) {……}public static void main(String[] args) {print(3);print(1.2f);print("hello!");}
}
通过对象调用方法时,通过(方法名 → to →参数列表)确定用哪一个方法
可变个数形参:
jdk5.0新增的方法
示例:
public class TestStudent {public static void main(String[] args) {TestStudent testStudent = new TestStudent();testStudent.show(); //可以是0个testStudent.show("String1");testStudent.show("String1","String2");//动态改变参数个数}//public void show(String s){// System.out.println("show(String s)");//}//不注释的话testStudent.show("String1")会优先调用此方法public void show(String ... strs){ //只需要写一个方法System.out.println("show(String ... strs)");for(int i=0;i< strs.length;++i){ //内部取每个参数时与数组一样System.out.println(strs[i]);}}//public void show(String[] strs){// System.out.println("show(String[] strs)");//}//可变参数方法与形参类型相同的数组不构成重载,不能共存
}
注意:String ... strs
只能放在形参列表的最后一个
//Vararg parameter must be the last in the list
public void show(String ... strs,int a){}
⭐方法参数的值传递机制:
关于变量的赋值
基本数据类型变量:
int m = 10,n = 20;
System.out.println(m+","+n);
m=n;//此时赋值的是变量所保存的数据值
System.out.println(m+","+n);
//10,20
//20,20
引用数据类型变量:(实例、数组 )
Student student1 = new Student();student1.number = 1;
System.out.println(student1.number);Student student2 = student1;//此时赋值的是变量保存的指向堆中实例的数据地址值student2.number = 2;
System.out.println(student1.number);
//1
//2
方法的形参的传递机制:
形参:方法声明是的参数
实参:方法调用时,实际传给形参的值
Java中方法的参数传递只有一种:值传递
参数是基本数据类型:实参赋值给形参的只是实参实际存储的数据值,方法内对形参的操作,并不会影响到实参的值
public class TestStudent { public static void main(String[] args) { int m = 10; int n = 20; TestStudent testStudent = new TestStudent(); testStudent.swap(m,n); System.out.println(m+","+n);//输出:10,20 //调用了swap(),但m、n并没有交换 //类似问题:见下方附加题 } public void swap(int a,int b){ int temp = a; a=b; b=temp; }
}
参数是引用数据类型:实参赋值给形参的是实参存储数据的地址值
public class Student { int m; int n;
}
public class TestStudent { public static void main(String[] args) { TestStudent testStudent = new TestStudent(); Student student = new Student(); student.m=10; student.n=20; testStudent.swap(student); System.out.println(student.m+","+student.n); //输出结果20,10(实现了交换) } public void swap(Student stu){ int temp = stu.m; stu.m= stu.n; stu.n=temp; }
}
附加题:
public class Test { public static void main(String[] args) { int a = 10; int b = 20; method(a,b); //需要在调用method方法后,打印出a=100,b=200,写出method() System.out.println("a="+a); System.out.println("b="+b); }
}
//方法一:
public void method(int m,int n){ a=a*10; b=b*10; System.out.println("a="+a); System.out.println("a="+a); it(0); //直接在此退出程序,不让执行main()的两个打印语句,只能在输出语句上做文章
}//方法二:对println()方法重写
递归方法(recursion):
一个方法体内调用它自身
示例:
//实现1-100所有自然数的和
public class Recursion {public static void main(String[] args) {Recursion recursion = new Recursion();int sum = Sum(100);System.out.println(sum);}public int getSum(int n){if(n == 1){return 1;}else {return n + getSum(n-1);}}
}
//实现递归数列求值:f(0)=1;f(1)=4;f(n+2)=2*f(n+1)+f(n)
public class Recursion {public static void main(String[] args) {Recursion recursion = new Recursion();int key = recursion.feibo(10);System.out.println(key);}public int feibo(int n){if(n == 0){return 1;}else if(n == 1){return 4;}else{//return feibo(n+2)-2*feibo(n+1);错误:栈溢出return 2*feibo(n-1) + feibo(n-2);}}
}
程序设计追求高内聚低耦合:
封装性设计思想:
隐藏对象内部的复杂性,之对外公开简单的API,便于外界调用,从而提高系统的可扩展性、可维护性(把该隐藏的隐藏起来,该暴露的暴露出来)
示例1:
当创建一个类的对象时,可以通过对象.属性
来对对象进行赋值操作,但有时赋值操作要遵循属性的数据类型和存储范围的约束,同时实际问题中,需要给属性赋值添加额外的限制条件,这个条件不能在属性声明时体现,是能通过方法进行限制条件的添加
public class Student {String name;private int legs; //legs改动时有特定限制条件,故legs设为privatepublic void show(){System.out.println("name="+name+",legs="+legs);}public void setLegs(int l){if(l>=0&&l%2==0){legs=l;}else{legs=0;}}
}public class TestStudent {public static void main(String[] args) {Student student = new Student();student.name="abc";//student.legs=-3; //'legs' has private access in 'Student'student.setLegs(-6);student.show();}}
示例2:private int legs;
:legs的赋值限制了,但怎么获取到legs的值呢?
public void getLegs(){return legs;}
封装性的体现:
***
私有化(private),同时,提供公共的(publlic)方法来获取get***()
和设置set***()
私有属性四种访问权限修饰符:
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
注意:
构造器的==特点:==
权限修饰符 类名(形参列表){}
构造器的==作用:==
创建对象:对象类型 对象名 = new 构造器
public class Student {public Student(){ System.out.println("");}
}public class TestStudent {public static void main(String[] args) {Student student = new Student();}
}
初始化对象的属性:对象类型 对象名 = new 构造器(形参)
public class Student {String name;public Student(){System.out.println("");}public Student(String s){ name=s;System.out.println("Student Constructor(String s)...");}
}public class TestStudent {public static void main(String[] args) {//创建对象时直接初始化属性Student student = new Student("abc"); System.out.println(student.name);}
}
是一种Java语言写成的可重用组件
所谓JavaBean,是指符合如下标准的Java类
示例:
public class JavaBean {private String name;private int age;public JavaBean() { //空参构造器}public String getName() { //get(),set()方法return name;}public int getAge() {return age;}public void setName(String name) {this.name = name;}public void setAge(int age) {this.age = age;}
}
引入原因:
在写set()方法时,形参往往需要取符合实际意义的名字,则就造成了与类属性名的重名,只有重名时才显示的写出this,通常都会省略this
类似地,构造器中也可能出现这种重名情况
public void setName(String name) {name = name; //区分不出哪个是形参,哪个是类属性
}
引入this
关键字①修饰调用属性:
public void setName(String name) {this.name = name; //this.name就表示当前类的属性
}
this
表示当前对象,即在之后调用时,所创建、使用的那个对象
Student student = new Student();
student.setName("abc"); //this提前代指了这个名叫student的对象
this②修饰调用方法:
public class Student {public void eat(){this.study(); //谁调用eat(),就调用它的study()System.out.println("吃");}public void study(){System.out.println("学");}
}public class TestStudent {public static void main(String[] args) {Student student = new Student();student.eat();}
}
//学
//吃
this③修饰调用构造器:
引入原因:若对象初始化时又必要的操作,就需要在每一个构造器里都添加这些代码,这样就造成了代码的冗余
public class Student {private String name;private int age;public Student() {//此处有Student初始化时必要的操作如吃eat()...this.eat();}public Student(String name) {this.eat();this.name=name;}public Student(int age) {this.eat();this.age = age;}public Student(int age,String name) {this.eat();this.age = age;this.name=name;}public void eat(){System.out.println("吃");}
}
使用this
关键字:
public class Student {private String name;private int age;public Student() {this.eat();}public Student(String name) {this(); //使用this调用Student()构造器this.name=name;}public Student(int age,String name) {this(name); //使用this调用Student(String name)构造器this.age = age;}public void eat(){System.out.println("吃");}
}public class TestStudent {public static void main(String[] args) {Student student = new Student(20,"abc");//此时只使用Student(String name)构造器,也会执行前驱操作eat()}
}
注意:
this(形参列表)
,不能形成调用环this(形参列表)
必须声明在当前构造器的首行this(形参列表)
,用来调用其他构造器package
.
一次就代表一层文件目录,package 顶层包名.子包名 ;
import
在源文件中显式地使用import导入指定包下的类、接口
声明在包的声明和类的声明之间
如果使用的类或者接口是属于java.lang
包下定义的,则可以省略import导入
本包下定义的类或接口,也可以省略import导入
如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的
是哪个类
import com.demo.Student;public class TestStudent { public static void main(String[] args) { //写全类名,用于区分不同包下的同名类 company.Student student = new company.Student(20,"abc"); Student student1 = new Student(); }
}
如果已经导入java.a
包下的类。那么如果需要使用a包的子包下的类
的话,仍然需要导入
import static
:导入指定类或接口中的静态结构:属性或方法
import static java.lang.System.*;
public class TestStudent {public static void main(String[] args) {out.println("可以省略Syste");}
}
继承性的好处
继承性的格式:class A extends B
规则:
子类不能直接访问父类中私有的private
的成员变量和方法。能获取到,只是因为封装性,使得子类不能直接调用
子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的扩展
示例:
public class Person {String name;private int age = 20;String major;public void eat(){System.out.println("吃");}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
public class Student extends Person {public void study(){System.out.println("学");}public void showAge(){//System.out.println(age);//int age不报错,但private int age会报错System.out.println(getAge());//子类继承得到了父类的getAge()方法,便可访问父类私有属性}
}
public class TestStudent {public static void main(String[] args) {Student student = new Student();student.name="abc";student.eat();student.study();student.showAge();//即使private int age,但使用getAge()也可以获取到父类的age值}
}
Java只支持单继承和多层继承,不允许多重继承
Obeject类
如图:父类中并没有声明这些方法,但是父类对象可以调用
如果没有显式地声明一个类的父类的话,则此类继承于java.lang.Object
类,意味着所有的Java类具有java.lang.Object类声明的功能
定义:在子类继承父类后,可以对父类中同名、同参数的方法,进行覆盖操作
应用:重写后,当创建子类对象后,通过子类对象调用父类中的同名同参数的方法时,实际执行的时子类重写后的方法
示例:
public class Person {public void eat(){System.out.println("吃");}
}public class Student extends Person {public void eat(){System.out.println("学生要吃健康的食物");}
}
重写的规定:
子类重写的方法的方法名和形参列表与父类被重写的方法名和形参列表相同
子类重写的方法的权限修饰符不小于弗雷被重写的方法的权限修饰符
特殊的:子类不能重写父类中声明为private权限的方法
返回值类型:
父类中被重写的方法的返回值为void,则子类中重写的方法的返回值只能是void
父类中被重写的方法的返回值为基本数据类型,则子类中重写的方法的返回值只能是相同的基本数据类型
父类中被重写的方法的返回值为A类,则子类中重写的方法的返回值只能是A类或A的子类
public class Person {public Object eat(){System.out.println("吃");}
}public class Student extends Person {public String eat(){System.out.println("学生要吃健康的食物");}
}
子类重写的方法抛出的异常类型大小不小于父类被重写的方法抛出的异常类型
子类和父类中同名同参数的方法要么都声明成非static的(考虑重写),要么都声明成static的(一定不是重写)
区分方法的重载与重写:
引入原因:
super
关键字super的作用
我们可以在子类的方法或构造器当中,使用super.属性
或this.属性
的方式,显式的调用父类中声明的属性或方法,但是,通常情况下会将关键字省略
特殊情况下:当子类和父类定义了相同名的属性时,就需要使用关键字区分
示例:
public class Person {String name;int age = 20;int id = 10001;}public class Student extends Person {int id = 10001;public void show(){//通常会将this与super关键字省略不写System.out.println("age="+age+","+"name="+name);//当子类和父类的属性名相同时,就需要加上关键字加以区分System.out.println("父类id="+super.id+"子类id="+","+this.id);}
}
调用父类中未被重写的方法
示例:
public class Person {public void eat(){System.out.println("吃");}
}public class Student extends Person {public void study(){eat(); //子类中重写的super.eat(); //父类中未被重写的System.out.println("学");}public void eat(){System.out.println("学生要吃健康的食物");}
}public class TestStudent {public static void main(String[] args) {Student student = new Student();student.study();}
}
//学生要吃健康的食物
//吃
//学
super.
会直接在父类中找,如果找不到就一直向上找对应的方法或属性,this.
会直接在本类中找
super调用构造器
之前提到当父类的属性设置为private后,在子类的构造其中并不能使用this关键字获取到属性,只能使用父类中提供的get()方法
public class Person {String name;private int age = 20;public Person(){}public Person(int age) {this.age = age;}
}public class Student extends Person {public Student() {}public Student(int age,String name) {this.name = name;//this.age = age; //'age' has private access in 'company.Person'}
}
使用super()
调用父类的构造器
public class Student extends Person {public Student() {}public Student(int age,String name) {//这里直接调用了父类的构造器:public Person(int age)super(age);this.name = name;}
}
在调用构造器时,super(形参列表)
的使用,必须声明在子类构造器的首行,这就与this(形参列表)
调用构造器也必须声明在首行,形成矛盾,故在类的构造器中,对于super与this关键的使用只能二选一
故在任意的子类构造器中都会首先调用父类的空参构造器,存在一个看不见的super()
,故在写父类时习惯性的添加空参构造器,否则子类的构造器会报错
public class Person {String name;int age = 20;public Person(){System.out.println("我被调用了");}
}public class Student extends Person {public Student() {}public Student(int age,String name) {//如果构造器中没有显式地使用过super()或this()//则这里有一个默认的super(无参)this.age = age;this.name = name;}
}public class TestStudent {public static void main(String[] args) {Student student = new Student(28,"abd");}
}
//我被调用了
在类的构造器中,至少有一个使用了super(形参列表)
,调用父类的构造器,具体见子类对象实例化的过程
从结果上来看:(继承性)
从过程上来看:
当我们通过子类构造器创建子类对象时,我们一定会直接或间接的调用父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object
类中的空参的构造器为止
(图中一个小矩形为一个构造器)
正因为加载过所有父类的结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑进行调用
明确:
创建子类实例的过程中,虽然调用了父类的构造器,但至始至终就创建了一个对象
多态性:
可以理解为一个事物的多种形态
对象的多态性:
父类的引用指向子类的对象(子类的对象赋给父类的引用)
多态的使用:
有了对象的多态性以后,我们在编译期,只能调用父类声明的方法,但在运行期间,实际执行的是子类重写后的方法(编译看左边,运行看右边)
//对象的多态性:父类的引用指向子类的对象
Person person1 = new man();//⭐
Person person2 = new woman();//虚拟方法调用
//多态的使用:当调用子父类同名同参的方法时,实际执行的是子类重写过的方法
person1.eat();
person2.eat();//person1.earnMoney();
//⭐编译期间,认为person1是父类对象,不能调用子类特有的方法
// Cannot resolve method 'earnMoney' in 'Person'
多态性使用的前提:
为什么要有多态性?
示例1:
public class Person {public void eat(){System.out.println("吃");}public void walk(){System.out.println("走路");}
}
package company;public class man extends Person{public void eat(){System.out.println("男人吃");}
}
package company;public class woman extends Person{public void eat(){System.out.println("女人吃");}
}
public class PersonTest {public static void main(String[] args) {PersonTest personTest = new PersonTest();personTest.func(new man());//⭐personTest.func(new woman());}public void func(Person person){//Person person = new man()person.eat(); //传入哪个子类实例,就执行相应的子类重写后的方法person.walk();}
}
如果没有多态性:
public class PersonTest {public static void main(String[] args) {PersonTest personTest = new PersonTest();personTest.func(new man());personTest.func(new woman());}//如果没有多态性,就需要写很多重载的func()public void func(man man){man.eat();man.walk();}public void func(woman woman){woman.eat();woman.walk();}
}
示例2:
class Order{public void method(Object obj){}
}
示例3:
class Driver{//Connection conn = new MySQLConnection //Connection conn = new OracleConnection //不同的数据库new不同的子类实例public void doData(Connection conn){hod1();//从MySQL切换到Oracl方法体内的代码不需要改hod2();}
}
对象的多态性只适用于方法,不适用于属性
public class PersonTest {public static void main(String[] args) {PersonTest personTest = new PersonTest();Person person = new man();System.out.println(man.id);//输出父类中的id}
}
instanceof操作符
有了对象的多态性以后,内存中实际是加载了子类特有的属性和方法的new man()
,但是由于变量声明为父类类型Person person
,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法并不能调用
如何才能调用子类特有的属性和方法
使用强制类型转换符向下转换
public class PersonTest {public static void main(String[] args) {Person person = new man();//多态man man1 = (man)person;//使用强转后就可以调子类特有的的属性和方法man1.earnMoney();man1.isSmoking = true;}
}
使用强转时可能出现ClassCastException
异常
woman woman1 = (woman) person;
Shopping();
//Person person = new man():personnew的时候是一个man
为了避免出现上述异常,引入instanceof
关键字
instanceof关键字的使用
使用情形:为了避免在向下转型时出现ClassCastException异常,我们在向下转型之前,先进性instanceof的判断,一旦返回为ture,就向下转型
public static void main(String[] args) {Person person = new man();man man1 = (man)person;if(person instanceof woman){//判断为false不会执行woman woman1 = (woman)Shopping();}
}
如果a instanceof A
返回true,则b instanceof B
也返回true,其中B是A的父类
开发中使用很少,但也可以用
public void func(Person person){//Person person = new man()person.eat();person.walk();if(person instanceof man){((man) person).earnMoney();}
}
面试题:多态是编译时行为还是运行时行为?
import java.util.Random;class Animal { protected void eat() {System.out.println("animal eat food");}
}class Cat extends Animal { protected void eat() {System.out.println("cat eat fish");}
}class Dog extends Animal { public void eat() {System.out.println("Dog eat bone");}
}class Sheep extends Animal { public void eat() {System.out.println("Sheep eat grass");}
}public class InterviewTest {public static Animal getInstance(int key) {switch (key) {case 0:return new Cat ();case 1:return new Dog ();default:return new Sheep ();}}public static void main(String[] args) {int key = new Random().nextInt(3);System.out.println(key);Animal animal = getInstance(key);//只有随机数key运行是确定后,才能确定到底执行哪个子类的方法animal.eat(); }
}
Object类是所有Java类的根父类
如果在类的声明中未使用extends关键字指明其父类, 则默认父类为java.lang.Object类
public class PersonTest {public static void main(String[] args) {Person person = new Person();System.out.Class().getSuperclass());}
}
//class java.lang.Object
Object类中的功能(属性、方法)具有通用性
clone()
:复制一个对象equals()
:比较两个对象是否相等finalize()
:发现堆空间实体没有任何引用指向它,则将其回收(注:回收之前调用此方法)注:final、finally、finalize的区别getClass()
反射hashCode()
集合notify()
多线程notifyAll()
toString()
wait()
多线程wait(long timeout)
wait(long timeout,int nanos)
Object类中只有一个空参构造器
equals( ) 方法
==
运算符的回顾
public class equalsTest {public static void main(String[] args) {//基本数据类型int i = 10;int j = 10;System.out.println(i == j);//truedouble d = 10.0;System.out.println(i == d);//trueboolean b = true;//System.out.println(i == b);char c = 10;System.out.println(i == c);//truechar c1 = 'A';char c2 = 65;System.out.println(c1 == c2);//true//引用类型Person person1 = new Person("abc",22);Person person2 = new Person("abc",22);System.out.println(person1 == person2);//false}
}
equals( )的使用:
是一个方法而不是运算符
只能适用于引用数据类型
Obejct类中equals( )的定义:和 == 作用是相同的(比较地址值)
public boolean equals(Object obj) {return (this == obj);
}
Person person1 = new Person("abc",22);
Person person2 = new Person("abc",22);System.out.println(person1.equals(person2));
//false:调用的是Object中的equals()方法
像String、Date、File、包装类等都重写了Object类中的equals( )方法,重写后不是比较地址值,而是比较两个对象的内容是否相同
String str1 = new String("ABCD");
String str2 = new String("ABCD");
System.out.println(str1.equals(str2));
//true:调用的是String中重写的equals()方法
Date date1 = new Date(312312312L);
Date date2 = new Date(312312312L);
System.out.println(date1.equals(date2));
//true:调用的是Date中重写的equals()方法
自定义类该如何重写equals( )方法:
需要重写Objcet类的equals()方法
public class Person { String name; int age = 20; public boolean equals(Object obj) { if (this == obj) { return true; } if(obj instanceof Person){ Person person = (Person)obj;//向下强转 if(this.age == person.age && this.name.equals(person.name)){//这里age是基本数据类型比较时使用 == 比较 //name是String类型(引用型)比较需要使用equals()比较 return true; }else { return false; }
}
public class equalsTest { public static void main(String[] args) { Person person1 = new Person("abc",22); Person person2 = new Person("abc",22); System.out.println(person1.equals(person2)); //true:调用的是Person类中重写的equals()方法 }
}
也可以自动生成:
@Override
public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return age == person.age && name.equals(person.name);
}
toString( )方法
未重写:
public class equalsTest { public static void main(String[] args) { System.out.String()); }
}
//输出:company.Person@2d97b5(地址值)
//其实直接输出一个引用型实例就是调用了toString()
Object类中的toString( )方法:
public String toString() { return getClass().getName() + "@" + HexString(hashCode());
}
像String、Date、File、包装类等都重写了Object类中的toString( )方法,使得在调用对象的toString( )方法时,返回实体内容信息
自定义类也可以重写toString( )方法
@Override
public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + '}';
}
public class PersonTest { public static void main(String[] args) { Person person = new Person(); System.out.String()); }
}
//Person{name='null', age=20}
idea中的JUnit单元测试步骤:
选中要测试的方法
CTRL + SHIFT + T
勾选要测试的方法和属性
示例1:
@org.junit.Test
public void testEquals() { String str1 = "aa"; String str2 = "bb"; System.out.println(str1.equals(str2));
}
示例2:
@org.junit.Test
public void testEquals() {Object obj = new Person();Date date = (Date) obj;
}
针对八种基本数据类型定义相应的引用类型——包装类(封装类)
基本数据结构有了类的特点,就可以调用类中的方法;且有的方法形参是引用型,基本数据类型放不进去
基本类型、包装类与String类间的转换 :
基本类型 → to →包装类(装箱)
package company;public class shiftTest {public static void main(String[] args) {int num1 = 10;Integer in1 = new Integer(num1);System.out.String());//10Integer in2 = new Integer("123");System.out.String());//123Float f1 = new Float(12.3f);Float f2 = new Float("12.3");System.out.println(f1.equals(f2));//trueBoolean b1 = new Boolean(true);Boolean b2 = new Boolean("TrUe");//只要不是这四个字母,就全认为是falseSystem.out.println(b1);//trueSystem.out.println(b2);//truePerson person = new Person();System.out.println(person.isMale);//falseSystem.out.println(person.isFemale);//null}
}public class Person {boolean isMale;Boolean isFemale;//这里是Boolean引用类型,默认值为null
}
包装类 → to →基本数据类型(拆箱)
public class shiftTest {public static void main(String[] args) {Integer in1 = new Integer(12);int i1 = in1.intValue();//⭐System.out.println(i1);//12Float f1 = new Float(12.3f);System.out.println(f1.floatValue());//12.3}
}
自动装箱、自动拆箱——jdk5.0之后
public class shiftTest {public static void main(String[] args) {int in1 = 10;Integer integer1 = in1;//自动装箱System.out.String());int in2 = integer1.intValue();//自动拆箱System.out.println(in2);}
}
基本数据类型 → to →String类型
public class shiftTest {public static void main(String[] args) {//方式一:连接运算int in1 = 10;String str1 = in1 + "";System.out.println(str1);//方式二:调用String的vlaueOf()方法float f1 = 12.3f;String str2 = String.valueOf(f1);System.out.println(str2);Double d1 = new Double(12.4);String str3 = String.valueOf(d1);System.out.println(str3);}
}
String类型 → to →基本数据类型
public class shiftTest {public static void main(String[] args) {String str1 = "123";int in1 = Integer.parseInt(str1);System.out.println(in1+1); //124String str2 = "true1";boolean b1 = Boolean.parseBoolean(str2);System.out.println(b1); //false原因同上}
}
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下, 某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量
static静态的
static可以用来修饰:属性、方法、代码块、内部类
static修饰属性:
属性按是否使用static修饰分为静态属性 和 非静态属性(实例变量)
实例变量: 我们创建了类的多个对象,每个对象都独立拥有一套类中的非静态属性,当修改一个对象的非静态属性时,不会改变其他对象的属性值
静态变量: 我们创建了类的多个对象,每一个对象共享有同一个静态变量,当通过某一个对象修改静态变量时,会导致其他对象调用静态变量时也会变为修改过的值
public class Person {static String nation;
}public class PersonTest {public static void main(String[] args) {Person person1 = new Person();person1.nation = "CHN";Person person2 = new Person();person2.nation = "China";System.out.println(person1.nation);//China}
}
static修饰属性的其他说明
静态变量随着类的加载而加载,可以没有对象的情况下,就使用类去调用
Person.nation = "US";
静态变量加载早于对象的创建
由于类只会加载一次,所以静态变量只会在内存中保存一份,存在方法区的静态域中
总结:
能否调用: | 类变量 | 实例变量 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态属性举例:System.out
Math.PI
静态变量的内存解析:
static修饰方法:
随着类的加载而加载,可以通过类.静态方法
的方式调用静态方法
能否调用:
能否调用: | 静态方法 | 非静态方法 |
---|---|---|
类 | yes | no |
对象 | yes | yes |
静态方法中,只能调用静态的方法和属性
非静态方法中,既可以调用非静态方法或属性,也可以调用静态的方法或属性
在静态方法中不能使用this
或super
关键字
关于静态属性和静态方法的使用,要从生命周期的角度考虑
开发中:
Math.Arrays
static应用
public class Circle {private double radius;private int id;public Circle() {id = init++;total++;}public Circle(double radius) {//id = init++;//total++;this();//调用Circle()this.radius = radius;}private static int total;//创建圆的个数private static int init = 1001;//id初始值,被多个对象共享public double findArea(){return Math.PI * radius * radius;}public double getRadius() {return radius;}public int getId() {return id;}public static int getTotal() {return total;}
}public class CircleTest {public static void main(String[] args) {Circle circle1 = new Circle();Circle circle2 = new Circle();System.out.Id());//1001System.out.Id());//1002System.out.Total());//2}
}
什么是单例设计模式
饿汉式单例:(直接创建对象)
public class Bank {//提供私有化类的构造器private Bank() {}//内部创建类的对象//要求对象也必须声明成静态private static Bank bank = new Bank();//提供公共的静态的方法,返回类的对象public static Bank getInstance(){return bank;}
}public class SingleTest {public static void main(String[] args) {//使用类调用获取对象的方法Instance();}
}
懒汉式单例:(用对象的时候才创建,不用的时候只声明)
public class Bank {//提供私有化类的构造器private Bank() {}//内部声明类的对象private static Bank bank = null;//⭐//提供公共的静态的方法,返回类的对象public static Bank getInstance(){if(bank == null){ //不判断的话,会造成每调用一次就创建一个实例return bank = new Bank();}return bank;}
}public class SingleTest {public static void main(String[] args) {//使用类调用获取对象的方法Instance();}
}
区别:
饿汉式 | 懒汉式 | |
---|---|---|
缺点: | 对象加载时间过长 | 目前写法:线程安全问题 |
优点: | 饿汉是线程安全的 (同时抢票) | 延迟对象的创建 |
单例模式的优点:
由于单例模式只生成一个实例, 减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
举例:java.lang.Runtime
public class Runtime {private static final Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}/** ⭐Don't let anyone else instantiate this class */private Runtime() {}
单例模式的应用场景:
main( )
方法的使用:
main( )方法作为程序的入喉
main( )方法也是一个普通的静态方法
main( )方法也可以作为与控制台获得交互的方式(之前使用Scanner),使用形参String[] args
传入
右击 → to →moreRun → to →Modify Run Configuration → to →Build and run → to →Program argument → to →填入键入值,空格隔开
代码块的作用:初始化类、对象
只能使用static
修饰,分类:
示例:
import java.util.Objects;public class Person {static String nation;String name;int age;//构造器public Person(){}//代码块static {nation = "CHN";//静态变量初始化System.out.println("只要类加载就执行,且只执行一次");//eat();//不能调用非静态结构}static {//可以有多个,顺序执行}{//可以调用非静态结构age = 20;//属性初始化System.out.println("只要创建一个对象就执行一次");//也可以调用静态结构nation = "US";}{ }//方法public void eat(){System.out.println("吃");}
}public class PersonTest {public static void main(String[] args) {System.out.println(Person.nation);Person person1 = new Person();System.out.println(person1.age);Person person2 = new Person();}
}
//只要类加载就执行,且只执行一次
//CHN
//只要创建一个对象就执行一次
//20
//只要创建一个对象就执行一次
总结:对属性可以赋值的位置:执行优先级:1、2/3(看顺序)、4、5
final修饰一个类:
此类不能被其他类所继承,比如String类、System类
public final class Bank {}
public class CNBank extends Bank {}
//Cannot inherit from final 'company.Bank'
final修饰一个方法:
此方法不能被子类重写
//'eat()' cannot override 'eat()' in 'company.Person'; overridden method is fina
final修饰一个变量:
此时的“变量”就称为一个常量
//Cannot assign a value to final variable 'age'
final修饰属性:赋值方法
public class finalTest {final int A = 1;//显式赋值final int B;final int C;{C = 2;//方式二:代码块中}public finalTest() {//方式三:构造器中B = 1;}public finalTest(int b) {B = 3;//所有构造器都要写,可以赋不同的值}}
final修饰局部变量
public class finalTest {//修饰局部变量public void test1(){final int D = 1;}//修饰形参public void test2(final int E){System.out.println(E);}
}
尤其: 当使用final修饰形参时,表面形参是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦赋值以后,就只能在方法体内使用此形参,但不能进行重写赋值
思想:
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类
使用:可以用来修饰类、方法
abstract修饰类:(抽象类)
此类不能实例化
//'Person' is abstract; cannot be instantiated
抽象类中也需要构造器:子类实例化的时候一定也需要调用父类的构造器
开发中,都会提供抽象类的子类,让子类对象实例化
abstract修饰方法:(抽象方法)
抽象方法只有方法的声明,没有方法体
public abstract class Person {public abstract void eat();
}
包含抽象方法的类一定是个抽象类
public class Person {public abstract void eat();//Abstract method in non-abstract class
}
若子类中重写了父类(所有父类)的所有的抽象方法,此类可实例化;若子类中没有重写了父类的所有的抽象方法,则此类也是一个抽象类,需要用abstract修饰
abstract不能修饰:
匿名子类:
public abstract class Person {public void eat(){System.out.println("吃");}
}public class man extends Person {public void eat(){System.out.println("男人吃");}
}public class testPerson {public static void main(String[] args) {//①methedPerson(new man());//匿名对象(且实参传子类对象,是多态的体现)//②man man = new man();methedMan(man);//非匿名类-非匿名对象//③methedMan(new man());//非匿名类-匿名对象//④Person person = new Person() {//创建一个匿名子类的对象,但这个对象不匿名叫person@Override //自动重写public void eat() {System.out.println("匿名子类中重写的吃");}};methedPerson(person);//⑥methedPerson(new Person() {//创建一个匿名子类的对象,这个对象也匿名@Overridepublic void eat() {System.out.println("匿名子类-匿名对象");}});}public static void methedMan(man man){man.eat();}public static void methedPerson(Person person){person.eat();}
}
引入原因:
有时必须从几个类中派生出一个子类,继承他们所有的属性和方法。但是,Java不支持多重继承,有了接口就可以实现多重继承的效果
有时必须从几个类中抽取出一些共同的行为特征,而他们之间有没有is-a的关系,仅仅是具有相同的行为特征而已,如鼠标、键盘等都支持USB连接
接口使用interface
来定义,与类是并列的两个结构
如何定义接口:定义接口中的成员
JDK7及以前:只能定义全局常量和抽象方法
全局常量:public static final
(可以省略,变量一律认为是全局常量)
抽象方法:public abstract
public interface interface1 {public static final int MAX_SPEED = 7900;int MIN_SPEED = 1;public abstract void fly();void stop();
}
JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法和默认方法
接口中不能定义构造器,故接口不能实例化
Java开发中:接口通过类来实现(impletement)的方式使用
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖了接口中的所有抽象方法,则此实现类仍为一个抽象类
public class Plane implements interface1{@Overridepublic void fly() { //将接口中的所有抽象方法重写System.out.println("飞机飞");}@Overridepublic void stop() {System.out.println("飞机停");}
}public class interfaceTest {public static void main(String[] args) {Plane plane = new Plane();//重写后就可以创建实例了plane.fly();plane.stop();}
}
Java类可以实现多个接口 → to →弥补了单继承的缺陷
格式:class AA extends BB imlement CC,DD,EE
public class Bullet extends Object implements interface1 , interface2{@Overridepublic void fly() {}@Overridepublic void stop() {}@Overridepublic void attack() {}
}
接口和接口之间可以继承,且可以多继承
public interface interface3 extends interface1,interface2{}
一个类如果要实现interface3,那么次类中就要重写interface1和interface2中的所有抽象方法
接口的使用:
接口的具体使用,体现多态性
public interface USB {abstract void start();abstract void stop();
}public class Flash implements USB{@Overridepublic void start() {System.out.println("U盘开始工作");}@Overridepublic void stop() {System.out.println("U盘停止工作");}
}public class Computer {public void tansferData(USB usb){usb.start();System.out.println("具体传输数据细节");usb.stop();}
}public class USBtest {public static void main(String[] args) {Computer computer = new Computer();Flash flash = new Flash();computer.tansferData(flash);//形参声明是一个接口类,但实参是它的实现类}
}//U盘开始工作
//具体传输数据细节
//U盘停止工作
接口实际上可以看做是一种规范
开发中体会面向接口编程
创建接口匿名实现类的对象
package company;public class USBtest {public static void main(String[] args) {Computer computer = new Computer();//创建了接口的非匿名实现类的非匿名对象Flash flash = new Flash();computer.tansferData(flash);//创建了接口的非匿名实现类的匿名对象computer.tansferData(new Print());//创建了接口的匿名实现类的非匿名对象usbInterface usbInterface = new usbInterface() {@Overridepublic void start() {System.out.println("重写开始方法");}@Overridepublic void stop() {System.out.println("重写结束方法");}};computer.tansferData(usbInterface);//创建了接口的匿名实现类的匿名对象computer.tansferData(new usbInterface() {@Overridepublic void start() {System.out.println("重写开始方法");}@Overridepublic void stop() {System.out.println("重写结束方法");}});}
}
Java8的接口新特性
可以在接口中提供静态方法和默认方法
静态方法:接口中定义的静态方法,只能使用接口自己去调用(实现类的对象调用不了)
默认方法:如果实现类中重写了接口中的默认方法,调用时,仍然调用的是重写后的方法
public interface JDK8new {public static void method1(){System.out.println("接口中可以写静态方法");}public default void method2(){System.out.println("接口中可以写默认方法");}public default void method3(){System.out.println("接口中的method3");}
}public class JDK8newImp implements JDK8new{public void method2(){System.out.println("重写的默认方法method2");}
}public class JSK8test implements JDK8new{public static void main(String[] args) {JDK8newImp j = new JDK8newImp();//j.method1();hod1();//接口中定义的静态方法,只能使用接口去调用j.method2();//通过实现类的对象可以调用接口的默认方法}
}
问题1:当实现类的父类中与接口中有一个同名同参数的方法,那么使用该实现类的对象调用这个名字方法时,会调用谁的?
答:在实现类没有重写这个方法的前提下,默认调用的时父类中同名同参数的默认方法 (类优先原则)
public class superJDKnewImp {public void method3(){System.out.println("实现类的父类中的method3");}
}public class JDK8newImp extends superJDKnewImp implements JDK8new{}public class JSK8test implements JDK8new{public static void main(String[] args) {JDK8newImp j = new JDK8newImp();j.method3();//结果:实现类的父类中的method3}
}
问题2: 当实现类实现了两个接口时,但两个接口中有一个同名同参数的默认方法,那么调用时会调用哪个?
public interface JDK8new2 {public default void method3(){System.out.println("接口2中的method3");}
}public class JDK8newImp implements JDK8new,JDK8new2{
}//报错:company.JDK8newImp inherits unrelated defaults for method3() from types company.JDK8new and company.JDK8new2
那么要怎么实现呢? → to →必须在实现类中重写
public class JDK8newImp implements JDK8new,JDK8new2{@Overridepublic void method3() {System.out.println("必须重写才能解决两个接口中出现同名同参方法的使用");}
}
问题3: 之前可以在子类的方法中使用super关键字调用父类的方法,那我们想在实现类的方法中实现调用接口中的默认方法怎么办?
public class JDK8newImp extends superJDKnewImp implements JDK8new,JDK8new2{@Overridepublic void method3() {System.out.println("必须重写才能解决两个接口中出现同名同参方法的使用");}public void method4(){//⭐//调用自己定义的重写方法method3();//调用父类中声明的方法hod3();//调用接口中定义的方法hod3();}
}
举例:人-大脑,大脑离不开人,但将大脑作为人的一个变量,又不足以刻画大脑的功能,故将大脑看作人的一个内部类
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B就是外部类
内部类的分类:
成员内部类:
public class innerClassTest {class AA{}static class A1{}
}
局部内部类:方法内、代码块内、构造器内
public class innerClassTest {public void method(){class BB{}}{class CC{}}public innerClassTest() {class DD{}}
}
内部类的使用:
成员内部类:
作为外部类的成员
调用外部类的结构
public class innerClassTest {class AA{public void method1(){eat();//是innerClassTest.this.eat();的省略写法}}public void eat(){}
}
可以被static修饰
可以被四种不同权限符修饰(此时是成员)
作为一个类:
可以定义属性、方法、构造器
public class innerClassTest {class AA{String name;public AA() {}public void method1(){System.out.println(" ");}}
}
可以被final修饰:不能被继承
可以被abstract修饰:不能不被实例化
局部内部类(见下)
关注如下三个问题:
开发中局部内部类的使用
使用场景:
public class innerClassTest {//此方法的作用:返回一个实现了Comparable接口的实现类的对象实例public Comparable getComparable(){class MyComparables implements Comparable{@Overridepublic int compareTo(Object o) {return 0;}}return new MyComparables();//返回实现类的对象}
}
如何实例化成员内部类的对象
public class innerTest {public static void main(String[] args) {//创建静态的成员内部类的实例//对象类型:外部类.内部类innerClassTest.A1 a = new innerClassTest.A1();//创建非静态的成员内部类的实例//非静态不能使用类名直接去调用,先创建外部类的实例innerClassTest innerClassTest = new innerClassTest();innerClassTest.AA b = w AA();}
}
如何在成员内部类中调用外部类的结构
当外部类和内部类中有同名的结构时:
public class innerClassTest {String name;class AA{String name;public void method1(String name){System.out.println(name);//传入的形参System.out.println(this.name);//内部类AA中的nameSystem.out.println(innerClassTest.this.name);//外部类中的neme}}
}
本文发布于:2024-02-01 21:01:07,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170679246839394.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |