面向对象笔记
- java面向对象
- 4.1面向过程(POP)与面向对象(OOP)
- 4-2.Java语言的基本元素:类和对象
- 4.3.Java对象的创建和使用
- 4.4匿名对象的使用
- 4.5再谈方法
- 4-6面向对象特征之一:封装与隐藏
- 4-7类的成员之三:构造器(或构造方法constructor)
- 4.8拓展知识:JavaBean
- 4.9关键字:this的使用
- 4-10 关键字:package、import的使用
- 5-1 面向对象特征之二:继承性(inheritance)
- 5.2 方法的重写(override/overwrite)
- 5-3 关键字:super
- 5.5 子类对象实例化过程
- 5.6 面向对象特征之三:多态性
- 5-7 Object类的使用
- 5.8 包装类(Wrapper)的使用
- 6-1 关键字:static
- 6-3 类的成员之四:代码块
- 6.4 关键字:final
- 6-5 抽象类与抽象方法
- 6-6 接口(interface)
java面向对象
学习java面向对象内容的三条主线:(第4-6章)
- Java类及类的成员:属性、方法、构造器、代码块、内部类。
- 面向对象的三大特征:封装性、继承性、多态性、(抽象性)。
- 其它关键字,this、super、static、final、abstract、interface、package、import等。
面向对象的思想概述:
- 程序员从面向过程的执行者转化成了面向对象的指挥者。
- 面向对象分析方法分析间题的思路和步骤: 根据问题需要,选择问题所针对的现实世界中的实体。
1.从实体中寻找解决问题相关的属性和功能,这些展性和功能就形成了概念世界中的类。。
2.把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
3.将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
4.1面向过程(POP)与面向对象(OOP)
面向对象:Object Oriented Programming
面向过程:Procedure Oriented Programming
二者都是一种思想,面向对象是相对于面向过程而言的。
面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
面向对象的三大特征:
封装(Encapsulation)
继承(Inheritance)
多态(Polymorphism)
4-2.Java语言的基本元素:类和对象
- 类(Class)和对象(Object)是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义。
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
- “万事万物皆对象”。
例:面向对象中对人的理解:
可以理解为:类=抽象概念的人;对象=实实在在的某个人
面向对象程序设计的重点是类的设计
类的设计,其实就是类的成员的设计
如何理解万事万物皆对象?
- 1.在Java语言范畴中,我们都将功能结构等封装到类中,通过类的实例化来调用具体的功能结构,如:
Scanner,String
文件:File
网络资源:URL
等都是通过实例化的对象起作用的。
- 2.涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互时,都体现为类、对象。
Java类及类的成员
现实世界的生物体,大到鲸鱼,小到蚂蚁,都是由最基本的细胞构成的。同理,Java代码世界是由诸多个不同功能的类构成的。
现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质、…那么,Java中用类class来描述事物也是如此。常见的类的成员有:
属性:对应类中的成员变量
行为:对应类中的成员方法
Field=属性=成员变量,Method=(成员)方法=函数
4.3.Java对象的创建和使用
Java类的实例化,即创建类的对象。
一、设计类,其实就是设计类的成员
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
二、类和对象的使用(面向对象思想落地的实现):
1.创建类,设计类的成员。
2.创建类的对象。
3.通过“对象.属性”或“对象.方法”调用对象的结构 。
三、如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
四、对象的内存解析
-
堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
-
通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
-
方法区(Method Area),用于存储己被虚拟机加载的类信息、常量、静态变量、即时编译器编详后的代码等数据。
-
JVM内存结构:
//测试类
public class PersonTest {
public static void main(String[] args) {
//2. 创建Person类的对象
Person p1 = new Person();
//Scanner scanner = new Scanner(System.in);
//调用对象的结构:属性、方法
//调用属性:“对象.属性”
p1.name = "Tom";
p1.isMale = true;
System.out.println(p1.name);
//调用方法:“对象.方法”
p1.eat();
p1.sleep();
p1.talk("Chinese");
//*******************************
Person p2 = new Person();
System.out.println(p2.name);//null
System.out.println(p2.isMale);
//*******************************
//将p1变量保存的对象地址值赋给p3,导致p1和p3指向了堆空间中的同一个对象实体。修改p1、p3的效果一样。
Person p3 = p1;
System.out.println(p3.name);//Tom
p3.age = 10;
System.out.println(p1.age);//10
}
}
//1.创建类,设计类的成员
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);
}
}
内存解析的说明:引用类型的变量只可能存储两类值:null或地址值(含变量类型)。
对象数组的内存解析:
类中属性的使用
- 语法格式:
属性(成员变量) vs 局部变量
1.相同点:
1.1 定义变量的格式:数据类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都有其对应的作用域
2.不同点:
2.1 在类中声明的位置的不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。
2.2 关于权限修饰符的不同
属性:可以在声明属性时,指明其权限,使用权限修饰符。
常用的权限修饰符:private、public、缺省、protected —>封装性
目前,大家声明属性时,都使用缺省就可以了。
局部变量:不可以使用权限修饰符。
2.3对象属性的默认初始化赋值
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型。默认初始化值的情况:
属性(成员变量):类的属性,根据其类型,都有默认初始化值。
整型(byte、short、int、long):0
浮点型(float、double):0.0
字符型(char):0 (或’\u0000’)
布尔型(boolean):false
引用数据类型(类、数组、接口):null
局部变量:没有默认初始化值。意味着,我们在调用局部变量之前,一定要显式赋值。特别地:形参在调用时,我们赋值即可。
2.4 在内存中加载的位置:
属性:加载到堆空间中 (非static、static型成员变量不在堆中)。
局部变量:加载到栈空间。
成员变量(属性)和局部变量的区别?
区别 | 成员变量 | 局部变量 |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间或静态域内 | 栈空间 |
类中方法的声明和使用
什么是方法(method、函数):
方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
将功能封装为方法的目的是,可以实现代码重用,简化代码。
Java里的方法不能独立存在,所有的方法必须定义在类里。
举例:
public class Person{
private int age;
public int getAge(){ //声明方法getAge()
return age;
}
public void setAge(int i){//声明方法setAge()
age =i; //将参数i的值赋给类的成员变量age
}
}
- 方法的声明:
注意:
1.方法调用一次,就会被执行一次。
2.没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中不必使用return语句。如果使用,仅用来结束方法。
3.定义方法时,方法的结果应该返回给调用者,交由调用者处理。
4.方法中只能调用方法或属性,不可以在方法内部定义方法。 - 方法的分类:按照是否有形参及返回值
\ | 无返回值 | 无返回值 |
---|---|---|
无形参 | void方法名(){} | 返回值的类型方法名() {} |
有形参 | void方法名(形参列表){} | 返回值的类型 方法名(形参列表){} |
4.4匿名对象的使用
三、匿名对象的使用
1.理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象。
2.特征:匿名对象只能调用一次。
3.使用情况:
如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
我们经常将匿名对象作为实参传递给一个方法调用。
public class InstanceTest {
public static void main(String[] args) {
Phone p = new Phone();
// p = null;
System.out.println(p);
p.sendEmail();
p.playGame();
//匿名对象
// new Phone().sendEmail();
// new Phone().playGame();
new Phone().price = 1999;
new Phone().showPrice();//0.0
//**********************************
PhoneMall mall = new PhoneMall();
// mall.show(p);
//匿名对象的使用
mall.show(new Phone());
}
}
class PhoneMall{
public void show(Phone phone){
phone.sendEmail();
phone.playGame();
}
}
class Phone{
double price;//价格
public void sendEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
public void showPrice(){
System.out.println("手机价格为:" + price);
}
}
4.5再谈方法
4.5.1方法的重载(overload)
重载的概念:
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
重载的特点:
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
重载示例:
返回两个整数的和
int add(int x,int y){return x+y:}
返回三个整数的和
int add(int x,int y,int z){return x+y+z:}
返回两个小数的和
double add(double x,double y){return x+y:}
public class OverLoadTest {
public static void main(String[] args) {
OverLoadTest test = new OverLoadTest();
test.getSum(1,2);
}
//如下的4个方法构成了重载
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double d1,double d2){
System.out.println("2");
}
public void getSum(String s ,int i){
System.out.println("3");
}
public void getSum(int i,String s){
System.out.println("4");
}
//如下的3个方法不能与上述4个方法构成重载
// public int getSum(int i,int j){
// return 0;
// }
// public void getSum(int m,int n){
//
// }
// private void getSum(int i,int j){
//
// }
}
4.5.2可变形参的方法:可变个数的形参
JavaSE5.0中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个最可变的实参。
//JDK5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a String books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
说明:
1.声明格式:方法名(参数的类型名…参数名)
2.可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
3.可变个数形参的方法与同名的方法之间,彼此构成重载
4.可变参数方法的使用与方法参数部分使用数组是一致的
5.方法的参数部分有可变形参,需要放在形参声明的最后
6.在一个方法的形参位置,最多只能声明一个可变个数形参。
// 可变个数形参的方法
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(12);
// test.show("hello");
// test.show("hello","world");
// test.show();
test.show(new String[]{"AA","BB","CC"});
}
public void show(int i){
}
public void show(String s){
System.out.println("show(String)");
}
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){
//
// }
//The variable argument type String of the method
//show must be the last parameter
// public void show(String ...strs,int i){
//
// }
}
4.5.3方法参数的值传递机制
- 方法,必须由其所在类或对象调用才有意义。
形参:方法声明时的参数。方法定义时,声明的小括号内的参数。
实参:方法调用时实际传给形参的参数值
Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。值传递机制如下:
形参是基本数据类型,将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。
- 例程1:m和n的交换不成功
// 例程1:m和n的交换不成功
public class ValueTransferTest1 {
public static void main(String[] args) {
int m = 10;
int n = 20;
System.out.println("m = " + m + ", n = " + n);
//交换两个变量的值的操作
// int temp = m ;
// m = n;
// n = temp;
ValueTransferTest1 test = new ValueTransferTest1();
test.swap(m, n);
System.out.println("m = " + m + ", n = " + n);
}
public void swap(int m,int n){
int temp = m ;
m = n;
n = temp;
}
}
- 例程2: data.m和data.n的交换成功
//例程2: data.m和data.n的交换成功
public class ValueTransferTest2 {
public static void main(String[] args) {
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + data.m + ", n = " + data.n);
//交换m和n的值
// int temp = data.m;
// data.m = data.n;
// data.n = temp;
ValueTransferTest2 test = new ValueTransferTest2();
test.swap(data);
System.out.println("m = " + data.m + ", n = " + data.n);
}
public void swap(Data data){
int temp = data.m;
data.m = data.n;
data.n = temp;
}
}
class Data{
int m;
int n;
}
例程2内存解析:
- 数组排序中的例子(启发:更改数组中的元素值就要“在地址上做更改”):
// 数组排序
public void sort(int[] arr) {
// 冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// int temp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
//错误的:
// swap(arr[j],arr[j + 1]);
//正确的:
swap(arr,j,j + 1);
}
}
}
}
//错误的:交换数组中指定两个位置元素的值
// public void swap(int i,int j){
// int temp = i;
// i = j;
// j = temp;
// }
//正确的:交换数组中指定两个位置元素的值
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
4.5.4递归(recursion)方法
- 递归方法:一个方法体内调用它自身。
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
- 递归例程:
public static void main(String[] args) {
// 例1:计算1-100之间所有自然数的和
// 方式一:
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println(sum);
// 方式二:
RecursionTest test = new RecursionTest();
int sum1 = test.getSum(100);
System.out.println(sum1);
System.out.println("*****************");
int value = test.f(10);
System.out.println(value);
}
// 例1:计算1-n之间所有自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
// 例2:计算1-n之间所有自然数的乘积:n!
public int getSum1(int n) {
if (n == 1) {
return 1;
} else {
return n * getSum1(n - 1);
}
}
//例3:已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
// return f(n + 2) - 2 * f(n + 1);
return 2*f(n - 1) + f(n - 2);
}
}
4-6面向对象特征之一:封装与隐藏
为什么需要封装?封装的作用和含义?
我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
我们程序设计追求“高内聚,低耦合”。
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 :仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
- 隐藏一个类中不需要对外提供的实现细节;
- 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
- 便于修改,增强代码的可维护性;
/*
* 面向对象的特征一:封装与隐藏 3W:what? why? how?
* 一、问题的引入:
* 当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。
* 这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,
* 在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,
* 我们只能通过方法进行限制条件的添加。(比如:setLegs()).同时,我们需要避免用户
* 再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private). -->此时,针对
* 于属性就体现了封装性。
二、封装性的体现:
我们将类的属性xxx私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值.
拓展:
封装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 ...
三、封装性的体现,需要权限修饰符来配合。
1.Java规定的4种权限(从小到大排列):private、缺省、protected 、public
2.4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
3.具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
*/
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
// a.age = 1;
// a.legs = 4;//The field Animal.legs is not visible
a.show();
// a.legs = -4;
// a.setLegs(6);
a.setLegs(-6);
// a.legs = -4;//The field Animal.legs is not visible
a.show();
System.out.println(a.name);
}
}
class Animal{
String name;
private int age;
private int legs;//腿的个数
//对属性的设置
public void setLegs(int l){
if(l >= 0 && l % 2 == 0){
legs = l;
}else{
legs = 0;
// 抛出一个异常(暂时没有讲)
}
}
//对属性的获取
public int getLegs(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
//提供关于属性age的get和set方法
public int getAge(){
return age;
}
public void setAge(int a){
age = a;
}
}
四种访问权限修饰符:
- Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
- 对于class的权限修饰只可以用public和default(缺省)。
public类可以在任意地方被访问。
default类只可以被同一个包内部的类访问。
4-7类的成员之三:构造器(或构造方法constructor)
- 构造器的特征
它具有与类相同的名称
它不声明返回值类型。(与声明为void不同)
不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
构造器的作用:创建对象\给对象进行初始化
如:Order o=new Order();Person p=new Person(“Peter",15)
如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
语法格式:
修饰符 类名(参数列表){
初始化语句;
}
举例:
public class Animal{
private int legs; //构造器
public Animal(){
legs = 4;
}
public void setLegs(int i){
legs = i;
}
public int getLegs(){
return legs;
}
}
创建Animal类的实例:Animal a=new Animal();
调用构造器,将legs初始化为4。
根据参数不同,构造器可以分为如下两类:
1.隐式无参构造器(系统默认提供)
2.显式定义一个或多个构造器(无参、有参)
注意:
1)Java语言中,每个类都至少有一个构造器
2)默认构造器的修饰符与所属类的修饰符一致
3)一旦显式定义了构造器,则系统不再提供默认构造器
4)一个类可以创建多个重载的构造器
5)父类的构造器不可被子类继承
/*
* 类的结构之三:构造器(或构造方法、constructor)的使用
* construct:建设、建造。 construction:CCB constructor:建设者
*
* 一、构造器的作用:
* 1.创建对象
* 2.初始化对象的信息
*
* 二、说明:
* 1.如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
* 2.定义构造器的格式:权限修饰符 类名(形参列表){}
* 3.一个类中定义的多个构造器,彼此构成重载
* 4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
* 5.一个类中,至少会有一个构造器。
*/
public class PersonTest {
public static void main(String[] args) {
//创建类的对象:new + 构造器
Person p = new Person();
p.eat();
Person p1 = new Person("Tom");
System.out.println(p1.name);
}
}
class Person{
//属性
String name;
int age;
//构造器
public Person(){
System.out.println("Person().....");
}
public Person(String n){
name = n;
}
//
public Person(String n,int a){
name = n;
age = a;
}
//方法
public void eat(){
System.out.println("人吃饭");
}
public void study(){
System.out.println("人可以学习");
}
}
构造器重载
- 构造器一般用来创建对象的同时初始化对象。如
class Person{
String name;
int age;
public Person(String n,int a){name=n;age=a;}
}
- 构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。构造器重载举例:
例程一:
public class Person{
public Person(String name,int age,Date d{(this(name,age);…}
public Person(String name,int age)(…}
public Person(String name,Date d)(…}
public Person()X…}
例程二:
构造器重载举例
public class Person{
private String name;
private int age;
private Date birthDate;
public Person(String n,int a,Date d){
name = n;
age = a;
birthDate = d;
}
public Person(String n,int a){
name = n;
age = a;
}
public Person(String n,Date d){
name = n;
birthDate = d;
}
public Person(String n){
name = n;
age = 30;
}
- 构造器重载,参数列表必须不同
总结:属性赋值过程
截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位置,并指明赋值的先后顺序。
- 赋值的位置:
①默认初始化
②显式初始化
③构造器中初始化
④通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序:①-②-③-④
/*
* 总结:属性赋值的先后顺序
* ① 默认初始化
* ② 显式初始化
* ③ 构造器中初始化
* ④ 通过"对象.方法" 或 "对象.属性"的方式,赋值
* 以上操作的先后顺序:① - ② - ③ - ④
*/
public class UserTest {
public static void main(String[] args) {
User u = new User();
System.out.println(u.age);
User u1 = new User(2);
u1.setAge(3);
u1.setAge(5);
System.out.println(u1.age);
}
}
class User{
String name;
int age = 1;
public User(){
}
public User(int a){
age = a;
}
public void setAge(int a){
age = a;
}
}
4.8拓展知识:JavaBean
- JavaBean是一种Java语言写成的可重用组件。
- 所谓javaBean,是指符合如下标准的Java类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、set方法
/*
* JavaBean是一种Java语言写成的可重用组件。
所谓JavaBean,是指符合如下标准的Java类:
>类是公共的
>有一个无参的公共的构造器
>有属性,且有对应的get、set方法
*/
public class Customer {
private int id;
private String name;
public Customer(){
}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}
}
- 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
JavaBean示例
public class JavaBean{
private String name;//属性一般定义为private
private int age;
public JavaBean(){
}
public int getAge()
return age;
}
public void setAge(int a){
age a;
}
public String getName(){
return name;
}
public void setName(String n){
name n;
}
}
4.8拓展知识:UML类图
1.+表示public类型,-表示private类型,#表示protected类型
2.方法的写法:
方法的类型(+、-)方法名(参数名:参数类型):返回值类型
4.9关键字:this的使用
在Java中,this关键字比较难理解,它的作用和其词义很接近。
它在方法内部使用,即这个方法所属对象的引用;
它在构造器内部使用,表示该构造器正在初始化的对象。
- this可以调用类的属性、方法和构造器
- 什么时候使用this关键字呢?
1.当在方法内需要用到调用该方法的对象时,就用this。
2.具体的:我们可以用this来区分属性和局部变量。比如:this.name=name;
- 在任意方法或构造器内,如果使用当前类的成员变量或成员方法可以在其前面添加this,增强程序的阅读性。不过,通常我们都习惯省略this。
- 当形参与成员变量同名时,如果在方法内或构造器内需要使用成员变量,必须添加this来表明该变量是类的成员变量。
3.使用this访问属性和方法时,如果在本类中未找到,会从父类中查找。 - 使用this调用属性、方法:
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
public void getInfo(){
System.out.println(“姓名:” + name) ;
this.speak();
}
public void speak(){
System.out.println(“年龄:” + this.age);
}
}
/*
* this关键字的使用:
* 1.this可以用来修饰、调用:属性、方法、构造器
* 2.this修饰属性和方法:
* this理解为:当前对象 或 当前正在创建的对象
* 2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
* 2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用"this.变量"的方式,表明此变量是属性,而非形参。
* 3. this调用构造器
* ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
* ② 构造器中不能通过"this(形参列表)"方式调用自己
* ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
* ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
* ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
p1.eat();
System.out.println();
Person p2 = new Person("Jerry",20);
System.out.println(p2.getAge());
}
}
class Person{
private String name;
private int age;
public Person(){
// this.eat();
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
public Person(String name){
this();
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
//this.age = age;
//Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age; //和return age;效果相同。
}
public void eat(){
System.out.println("人吃饭");
this.study(); //和study();效果一样。
}
public void study(){
System.out.println("人学习");
}
}
- 使用this调用本类的构造器:this可以作为一个类中构造器相互调用的特殊格式。
class Person{ // 定义Person类
private String name ;
private int age ;
public Person(){ // 无参构造器
System.out.println(“新对象实例化”) ;
}
public Person(String name){
this(); // 调用本类中的无参构造器
this.name = name ;
}
public Person(String name,int age){
this(name) ; // 调用有一个参数的构造器
this.age = age;
}
public String getInfo(){
return “姓名:” + name + “,年龄:” + age ;
}
}
注意:
1)可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
2)明确:构造器中不能通过"this(形参列表)“的方式调用自身构造器
3)如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)”
4)"this(形参列表)“必须声明在类的构造器的首行!
5)在类的一个构造器中,最多只能声明一个"this(形参列表)”
练习
1.按照如下的 UML 类图,创建相应的类,提供必要的结构
在提款方法 withdraw()中,需要判断用户余额是否能够满足提款数额的要求,如果不能,应给出提示。deposit()方法表示存款。
public class Account {
private double balance;
public Account(double init_balance){
this.balance = init_balance;
}
public double getBalance(){
return balance;
}
//存钱操作
public void deposit(double amt){
if(amt > 0){
balance += amt;
System.out.println("存钱成功");
}
}
//取钱操作
public void withdraw(double amt){
if(balance >= amt){
balance -= amt;
System.out.println("取钱成功");
}else{
System.out.println("余额不足");
}
}
}
- 按照如下的 UML 类图,创建相应的类,提供必要的结构
public class Customer {
private String firstName;
private String lastName;
private Account account;
public Customer(String f, String l) {
this.firstName = f;
this.lastName = l;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
- 按照如下的 UML 类图,创建相应的类,提供必要的结构
1)addCustomer 方法必须依照参数(姓,名)构造一个新的 Customer 对象,然后把
它放到 customer 数组中。还必须把 numberOfCustomer 属性的值加 1。
2)getNumOfCustomers 方法返回 numberofCustomers 属性值。
3)getCustomer 方法返回与给出的 index 参数相关的客户。
public class Bank {
private Customer[] customers;// 存放多个客户的数组
private int numberOfCustomers;// 记录客户的个数
public Bank() {
customers = new Customer[10];
}
// 添加客户
public void addCustomer(String f, String l) {
Customer cust = new Customer(f, l);
// customers[numberOfCustomers] = cust;
// numberOfCustomers++;
// 或
customers[numberOfCustomers++] = cust;
}
// 获取客户的个数
public int getNumOfCustomers() {
return numberOfCustomers;
}
// 获取指定位置上的客户
public Customer getCustomer(int index) {
// return customers[index];//可能报异常
if (index >= 0 && index < numberOfCustomers) {
return customers[index];
}
return null;
}
}
- 创建 BankTest 类,进行测试。
public class BankTest {
public static void main(String[] args) {
Bank bank = new Bank();
bank.addCustomer("Jane", "Smith");
//连续操作
bank.getCustomer(0).setAccount(new Account(2000));
bank.getCustomer(0).getAccount().withdraw(500);
double balance = bank.getCustomer(0).getAccount().getBalance();
System.out.println("客户:" + bank.getCustomer(0).getFirstName() + "的账户余额为:" + balance);
System.out.println("***********************");
bank.addCustomer("万里", "杨");
System.out.println("银行客户的个数为:" + bank.getNumOfCustomers());
}
}
4-10 关键字:package、import的使用
- package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:package 顶层包名.子包名 :
举例:pack1\pack2\PackageTest.java
package pack1.pack2; //指定类PackageTest属于包pack1.pack2
public class PackageTest{
public void display(){
System.out.println(“in method display()”);
}
}
- 包对应于文件系统的目录,package语句中,用 “.” 来指明包(目录)的层次;
- 包通常用小写单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx
源文件布局:
包的作用:
- 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式。
- 包可以包含类和子包,划分项目层次,便于管理。
- 解决类命名冲突的问题。
- 控制访问权限。
例:某航运软件系统包括:一组域对象、GUI和reports子系统
包的例程(day10工作目录下):
package com.atguigu.java2;
import java.lang.reflect.Field;
//对应第8条规则,Field在lang包下的子包reflect中,要显示导入
import java.util.*;
import com.atguigu.exer4.Account;
import com.atguigu.exer4.Bank;
import com.atguigu.java2.java3.Dog;
import static java.lang.System.*;
import static java.lang.Math.*;
/*
* 一、package关键字的使用
* 1.为了更好的实现项目中类的管理,提供包的概念
* 2.使用package声明类或接口所属的包,声明在源文件的首行
* 3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
* 4.每"."一次,就代表一层文件目录。
* 补充:同一个包下,不能命名同名的接口、类。
* 不同的包下,可以命名同名的接口、类。
* 二、import关键字的使用
* import:导入
* 1. 在源文件中显式的使用import结构导入指定包下的类、接口
* 2. 声明在包的声明和类的声明之间
* 3. 如果需要导入多个结构,则并列写出即可
* 4. 可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
* 5. 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
* 6. 如果使用的类或接口是本包下定义的,则可以省略import结构
* 7. 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
* 8. 使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入(如此例中的import java.lang.reflect.Field;//Field在lang包下的子包reflect中)
* 9. import static:导入指定类或接口中的静态结构:属性或方法。
*/
public class PackageImportTest {
public static void main(String[] args) {
String info = Arrays.toString(new int[]{1,2,3});
Bank bank = new Bank();
ArrayList list = new ArrayList();
HashMap map = new HashMap();
Scanner s = null;
System.out.println("hello!");
Person p = new Person();
Account acct = new Account(1000);
//全类名的方式显示
com.atguigu.exer3.Account acct1 = new com.atguigu.exer3.Account(1000,2000,0.0123);
//全类名的方式显示:Data在java.sql和java.util下都有声明
Date date = new Date();
java.sql.Date date1 = new java.sql.Date(5243523532535L);
Dog dog = new Dog();
Field field = null;
/*对应第9条规则:
导入import static java.lang.System.*;后System.out.println("hello");可简化如下:
导入import static java.lang.Math.*;后Math.round(123.434);可简化如下:
原因:out和round都是静态结构
*/
out.println("hello");
long num = round(123.434);
}
}
JDK中主要的包介绍
- java.lang----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能
- java.net----包含执行与网络相关的操作的类和接口。
- java.io ----包含能提供多种输入/输出功能的类。
- java.util----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
- java.text----包含了一些java格式化相关的类。
- java.sql----包含了java进行JDBC数据库编程的相关类/接口。
- java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 B/S C/S
MVC设计模式
MVC是常用的设计模式之一,将整个程序分为三个层次:视图模型层,控制器层,与
数据模型层。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式
使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程
序的耦合性。
模型层 model 主要处理数据
数据对象封装 model.bean/domain
数据库操作类 model.dao
数据库 model.db
视图层 view 显示数据
相关工具类 view.utils
自定义view view.ui
控制层 controller 处理业务逻辑
应用界面相关 controller.activity
存放fragment controller.fragment
显示列表的适配器 controller.adapter
服务相关的 controller.service
抽取的基类 controller.base
关键字—import
为使用定义在不同包中的Java类,需用import语句来引入指定包层次下所需要的类或全部类(.*)。import语句告诉编译器到哪里去寻找类。
语法格式:
import 包名. 类名;
应用举例:
import pack1.pack2.Test; //import pack1.pack2.*;表示引入pack1.pack2包中的所有结构
public class PackTest{
public static void main(String args[]){
Test t = new Test(); //Test类在pack1.pack2包中定义
t.display();
}
}
注意:
- 在源文件中使用import显式的导入指定包下的类或接口。
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个import语句即可。
- 举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
- import static组合的使用:调用指定类或接口下的静态的属性或方法。
5-1 面向对象特征之二:继承性(inheritance)
- 为描述和处理个人信息,定义类Person:
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
//…
}
}
- 为描述和处理学生信息,定义类Student:
class Student {
public String name;
public int age;
public Date birthDate;
public String school;
public String getInfo() {
// …
}
}
- 通过继承,简化Student类的定义:
class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
// …
}
}
class Student extends Person {
public String school;
}
-
Student类继承了父类Person的所有属性和方法,并增加了一个属性school。Person中的属性和方法,Student都可以使用。
-
为什么要有继承?
1)多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
2)此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”。
3)类继承语法规则:
class Subclass extends SuperClass{ }
作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
2)继承的出现,更有利于功能的扩展。
3)继承的出现让类与类之间产生了关系,提供了多态的前提。
4)注意:不要仅为了获取其他类中某个功能而去继承。
继承的特征:
1)子类继承了父类,就继承了父类的方法和属性。
2)在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
3)在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。
4)特别地,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只是由于封装性的影响,使得子类不能直接调用父类的结构而已。
二、Java只支持单继承和多层继承,不允许多重继承。
1)单继承:一个子类只能有一个父类。
2)多层继承:一个父类可以派生出多个子类。
3)子父类是相对的概念。
4)子类直接继承的父类称为:直接父类。间接继承的父类称为:间接父类。
5)子类继承父类以后,就获得了直接父类以及间接父类中声明的所有属性和方法。
6)如果没有显示地声明一个类的父类的话,则此类继承与java.lang.Object类。即所有的java类(除去java.lang.Object类之外)都直接或间接地继承于java.lang.Object类。意味着所有java类都具有java.lang.Object类声明的属性和功能。
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2…//error
单继承与多层继承举例:
5.2 方法的重写(override/overwrite)
- 定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
- 要求:
1)子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
2)子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型:
父类被重写的方法的返回值是void,则子类重写的方法的返回值类型只能是void。
父类被重写的方法的返回值是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
父类被重写的方法的返回值是基本数据类型,则子类重写的方法的返回值类型只能是相同的基本数据类型。
3)子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。特殊情况:子类不能重写父类中声明为private权限的方法。
4)子类方法抛出的异常不能大于父类被重写方法的异常。
- 注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法。
重写举例一:
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + “\n” +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name=“Bob”;
s1.age=20;
s1.school=“school2”;
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
Person p1=new Person();
//调用Person类的getInfo()方法
p1.getInfo();
Student s1=new Student();
//调用Student类的getInfo()方法
s1.getInfo();
这是一种“多态性”:同名的方法,用不同的对
象来区分调用的是哪一个方法。
/*
* 方法的重写(override / overwrite)
* 1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
* 2.应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
* 3. 重写的规定:
* 方法的声明: 权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
* //方法体
* }
* 约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
* ① 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
* ② 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
>特殊情况:子类不能重写父类中声明为private权限的方法
* ③ 返回值类型:
* >父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
* >父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
* >父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
* ④ 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲) ***************************************************
* 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
* 面试题:区分方法的重载与重写
*/
5-3 关键字:super
- 在Java类中使用super来调用父类中的指定操作:
1)super可用于访问父类中定义的属性。
2)super可用于调用父类中定义的成员方法。
3)super可用于在子类构造器中调用父类的构造器。 - 注意:
1)尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员。
2)super的追溯不仅限于直接父类。
3)super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识。
关键字super举例:
class protected Person {
String name = “张三”;
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = “李四”;
private String school = “New Oriental”;
public String getSchool() {
return school;
}
public String getInfo() {
return super.getInfo() + "\nschool: " + school;
}}
public class StudentTest {
public static void main(String[] args) {
Student st = new Student();
System.out.println(st.getInfo());
}
}
- 调用父类的构造器
1)子类中所有的构造器默认都会访问父类中空参数的构造器。
2)当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行。
3)如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。
调用父类构造器举例
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
// 编译出错: no super(),系统将调用父类无参数的构造器。
public Student(String s) {
school = s;
}
}
this和super的区别
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
/*
* super关键字的使用
* 1.super理解为:父类的
* 2.super可以用来调用:属性、方法、构造器
* 3.super的使用:调用属性和方法
* 3.1 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用
* 父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
* 3.2 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的
* 使用"super.属性"的方式,表明调用的是父类中声明的属性。
* 3.3 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
* 使用"super.方法"的方式,表明调用的是父类中被重写的方法。
*
* 4.super调用构造器
* 4.1 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
* 4.2 "super(形参列表)"的使用,必须声明在子类构造器的首行!
* 4.3 我们在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二选一,不能同时出现
* 4.4 在构造器的首行,没有显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
* 4.5 在类的多个构造器中,至少有一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
*/
5.5 子类对象实例化过程
/*
* 子类对象实例化的全过程:
* 1. 从结果上来看:(继承性)
* 子类继承父类以后,就获取了父类中声明的属性或方法。
* 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
* 2. 从过程上来看:
* 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,...直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
* 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
*/
class Creature {
public Creature() {
System.out.println("Creature无参数的构造器");
}}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal带两个参数的构造器,其age为" + age);
}}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args) {
new Wolf();
}}
5.6 面向对象特征之三:多态性
多态性,是面向对象中最重要的概念,在Java中的体现:
- 对象的多态性:父类的引用指向子类的对象
可以直接应用在抽象类和接口上。
- Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
多态情况下,“看左边”:
看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:
看的是子类的对象(实际运行的是子类重写父类的方法)
- 对象的多态 —在Java中,子类的对象可以替代父类的对象使用:
1)一个变量只能有一种确定的数据类型
2)一个引用类型变量可能指向(引用)多种不同类型的对象
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
- 子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法。
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
/*
* 面向对象特征之三:多态性
* 1.理解多态性:可以理解为一个事物的多种形态。
* 2.何为多态性:
* 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
* 3. 多态的使用:虚拟方法调用
* 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。
* 总结:编译,看左边;运行,看右边。
* 4.多态性的使用前提: ① 类的继承关系 ② 方法的重写
* 5.对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
*/
package com.atguigu.java4;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
//*****************************************
System.out.println("*******************");
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();
p2.walk();
// p2.earnMoney();
System.out.println(p2.id);//1001
}
}
package com.atguigu.java4;
public class Man extends Person{
boolean isSmoking;
int id = 1002;
public void earnMoney(){
System.out.println("男人负责挣钱养家");
}
public void eat(){
System.out.println("男人多吃肉,长肌肉");
}
public void walk(){
System.out.println("男人霸气的走路");
}
}
package com.atguigu.java4;
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人:吃饭");
}
public void walk(){
System.out.println("人:走路");
}
}
虚拟方法调用(Virtual Method Invocation)
正常的方法调用:
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用(多态情况下):
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父
类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法
确定的。
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。——动态绑定
小结:方法的重载与重写
- 二者的定义细节:略.
- 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”;而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。
多态小结
- 多态作用:
提高了代码的通用性,常称作接口重用 - 前提:
需要存在继承或者实现关系。
有方法的重写。 - 成员方法:
编译时:要查看引用变量所声明的类中是否有所调用的方法。
运行时:调用实际new的对象所属的类中的重写方法。 - 成员变量:
不具备多态性,只看引用变量所声明的类。
instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
public void method1(Person e) {
if (e instanceof Person) // 处理Person类及其子类对象
if (e instanceof Student) //处理Student类及其子类对象
if (e instanceof Graduate)//处理Graduate类及其子类对象
}
对象类型转换 (Casting )
- 基本数据类型的Casting:
自动类型转换:小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
强制类型转换:可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
- 对Java对象的强制类型转换称为造型
从子类到父类的类型转换可以自动进行。
从父类到子类的类型转换必须通过造型(强制类型转换)实现。
无继承关系的引用类型间的转换是非法的。
在造型前可以使用instanceof操作符测试一个对象的类型。
- 对象类型转换举例
例程一:
public class ConversionTest {
public static void main(String[] args) {
double d = 13.4;
long l = (long) d;
System.out.println(l);
int in = 5;
// boolean b = (boolean)in;
Object obj = “Hello”;
String objStr = (String) obj;
System.out.println(objStr);
Object objPri = new Integer(5);
// 所以下面代码运行时引发ClassCastException异常
String str = (String) objPri;
}
}
例程二:
public class Test {
public void method(Person e) { // 设Person类中没有getschool() 方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if (e instanceof Student) {
Student me = (Student) e; // 将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(String[] args){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
- 子类继承父类
若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量
5-7 Object类的使用
- Object类是所有Java类的根父类:如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类。
public class Person {
…
}
等价于:
public class Person extends Object {
…
}
例:method(Object obj){…} //可以接收任何类作为其参数
Person o=new Person();
method(o)
/*
* java.lang.Object类
* 1.Object类是所有Java类的根父类
* 2.如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
* 3.Object类中的功能(属性、方法)就具有通用性。
* 属性:无
* 方法:equals() / toString() / getClass() /hashCode() / clone() / finalize()
* wait() 、 notify()、notifyAll()
* 4. Object类只声明了一个空参的构造器
* 面试题:
* final、finally、finalize的区别?
*/
- Object类中的主要结构
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时 |
- ==操作符与equals方法
= =:
基本类型比较值:只要两个变量的值相等,即为true。
int a=5; if(a= =6){…}
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,= = 才返回true。
Person p1=new Person();
Person p2=new Person();
if (p1= =p2){…}
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
equals():
所有类都继承了Object,也就获得了equals()方法。还可以重写。
只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;
原因:在这些类中重写了Object类的equals()方法。
当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等。
import java.util.Date;
/*
*
* 面试题: == 和 equals() 区别
*
* 一、回顾 == 的使用:
* == :运算符
* 1. 可以使用在基本数据类型变量和引用数据类型变量中
* 2. 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
* 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
* 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
*
* 二、equals()方法的使用:
* 1. 是一个方法,而非运算符
* 2. 只能适用于引用数据类型
* 3. Object类中equals()的定义:
* public boolean equals(Object obj) {
return (this == obj);
}
* 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
*
* 4. 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是
* 两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
*
* 5. 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们
* 就需要对Object类中的equals()进行重写.
* 重写的原则:比较两个对象的实体内容是否相同.
*/
public class EqualsTest {
public static void main(String[] args) {
//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);//true
System.out.println(i == d);//true
boolean b = true;
// System.out.println(i == b);
char c = 10;
System.out.println(i == c);//true
char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);//true
//引用类型:
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1 == cust2);//false
String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2);//false
System.out.println("****************************");
System.out.println(cust1.equals(cust2));//false--->true
System.out.println(str1.equals(str2));//true
Date date1 = new Date(32432525324L);
Date date2 = new Date(32432525324L);
System.out.println(date1.equals(date2));//true
}
}
重写equals()方法的原则
对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
自反性:x.equals(x)必须返回是“true”。
传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
任何情况下,x.equals(null),永远返回是“false”;
x.equals(和x不同类型的对象)永远返回是“false”。
- 类String里equals的重写源码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (!COMPACT_STRINGS || this.coder == aString.coder) {
return StringLatin1.equals(value, aString.value);
}
}
return false;
}
- 开发中可点击Source下Generate hashCode() and equals生成具体的重写的"比较两个对象的实体内容是否相同"的equals。
toString() 方法
toString()方法在Object类中定义,其返回值是String类型,返回类名和它的引用地址。
在进行String与其它类型数据的连接操作时,自动调用toString()方法:
Date now=new Date();
System.out.println(“now=”+now); 相当于
System.out.println(“now=”+now.toString());
可以根据需要在用户自定义类型中重写toString()方法,如String 类重写了toString()方法,返回字符串的值。
s1=“hello”;
System.out.println(s1);//相当于System.out.println(s1.toString());
基本类型数据转换为String类型时,调用了对应包装类的toString()方法
int a=10; System.out.println(“a=”+a);
package com.atguigu.java1;
import java.util.Date;
/*
* Object类中toString()的使用:
*
* 1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
*
* 2. Object类中toString()的定义:
* public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*
* 3. 像String、Date、File、包装类等都重写了Object类中的toString()方法。
* 使得在调用对象的toString()时,返回"实体内容"信息
*
* 4. 自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"
*/
public class ToStringTest {
public static void main(String[] args) {
// 1. 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()
Customer cust1 = new Customer("Tom",21);
System.out.println(cust1.toString());//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
System.out.println(cust1);//com.atguigu.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
String str = new String("MM");
System.out.println(str);//MM
Date date = new Date(4534534534543L);
System.out.println(date.toString());//Mon Sep 11 08:55:34 GMT+08:00 2113
}
}
5.8 包装类(Wrapper)的使用
针对八种基本数据类型定义相应的引用类型—包装类(封装类)有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
基本数据类型包装成包装类的实例 —装箱
通过包装类的构造器实现:
int i = 500; Integer t = new Integer(i);
还可以通过字符串参数构造包装类对象:
Float f = new Float(“4.56”);
Long l = new Long(“asdf”); //NumberFormatException
获得包装类对象中包装的基本类型变量 —拆箱
调用包装类的.xxxValue()方法:
boolean b = bObj.booleanValue();
JDK1.5之后,支持自动装箱,自动拆箱。但类型必须匹配。
字符串转换成基本数据类型
通过包装类的构造器实现:
int i = new Integer(“12”);
通过包装类的parseXxx(String s)静态方法:
Float f = Float.parseFloat(“12.1”);
基本数据类型转换成字符串
调用字符串重载的valueOf()方法:
String fstr = String.valueOf(2.34f);
更直接的方式:
String intStr = 5 + “”
package com.atguigu.java2;
import org.junit.Test;
/*
* 包装类的使用:
* 1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
* 2.掌握的:基本数据类型、包装类、String三者之间的相互转换
*/
public class WrapperTest {
//String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
@Test
public void test5(){
String str1 = "123";
//错误的情况:
// int num1 = (int)str1;
// Integer in1 = (Integer)str1;
//可能会报NumberFormatException
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1);
String str2 = "true1";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);
}
//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1);//"12.3"
Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3);//"12.4"
}
/*
* JDK 5.0 新特性:自动装箱 与自动拆箱
*/
@Test
public void test3(){
// int num1 = 10;
// //基本数据类型-->包装类的对象
// method(num1);
//自动装箱:基本数据类型 --->包装类
int num2 = 10;
Integer in1 = num2;//自动装箱
boolean b1 = true;
Boolean b2 = b1;//自动装箱
//自动拆箱:包装类--->基本数据类型
System.out.println(in1.toString());
int num3 = in1;//自动拆箱
}
public void method(Object obj){
System.out.println(obj);
}
//包装类--->基本数据类型:调用包装类Xxx的xxxValue()
@Test
public void test2(){
Integer in1 = new Integer(12);
int i1 = in1.intValue();
System.out.println(i1 + 1);
Float f1 = new Float(12.3);
float f2 = f1.floatValue();
System.out.println(f2 + 1);
}
//基本数据类型 --->包装类:调用包装类的构造器
@Test
public void test1(){
int num1 = 10;
// System.out.println(num1.toString());
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Integer in2 = new Integer("123");
System.out.println(in2.toString());
//报异常
// Integer in3 = new Integer("123abc");
// System.out.println(in3.toString());
Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("TrUe");
System.out.println(b2);
Boolean b3 = new Boolean("true123");
System.out.println(b3);//false
Order order = new Order();
System.out.println(order.isMale);//false
System.out.println(order.isFemale);//null
}
}
class Order{
boolean isMale;
Boolean isFemale;
}
包装类练习:
package com.atguigu.exer4;
import java.util.Scanner;
import java.util.Vector;
/* 题目描述:
* 利用Vector代替数组处理:从键盘读入学生成绩(以负数代表输入结束),找出最高分,并输出学生成绩等级。
提示:数组一旦创建,长度就固定不变,所以在创建数组前就需要知道它的长度。
而向量类java.util.Vector可以根据需要动态伸缩。
创建Vector对象:Vector v=new Vector();
给向量添加元素:v.addElement(Object obj); //obj必须是对象
取出向量中的元素:Object obj=v.elementAt(0);
注意第一个元素的下标是0,返回值是Object类型的。
计算向量的长度:v.size();
若与最高分相差10分内:A等;20分内:B等;
30分内:C等;其它:D等
*/
public class ScoreTest {
public static void main(String[] args) {
//1.实例化Scanner,用于从键盘获取学生成绩
Scanner scan = new Scanner(System.in);
//2.创建Vector对象:Vector v=new Vector();相当于原来的数组
Vector v = new Vector();
//3.通过for(;;)或while(true)方式,给Vector中添加数组
int maxScore = 0;
for(;;){
System.out.println("请输入学生成绩(以负数代表输入结束)");
int score = scan.nextInt();
//3.2 当输入是负数时,跳出循环
if(score < 0){
break;
}
if(score > 100){
System.out.println("输入的数据非法,请重新输入");
continue;
}
//3.1 添加操作::v.addElement(Object obj)
//jdk5.0之前:
// Integer inScore = new Integer(score);
// v.addElement(inScore);//多态
//jdk5.0之后:
v.addElement(score);//自动装箱
//4.获取学生成绩的最大值
if(maxScore < score){
maxScore = score;
}
}
//5.遍历Vector,得到每个学生的成绩,并与最大成绩比较,得到每个学生的等级。
char level;
for(int i = 0;i < v.size();i++){
Object obj = v.elementAt(i);
//jdk 5.0之前:
// Integer inScore = (Integer)obj;
// int score = inScore.intValue();
//jdk 5.0之后:
int score = (int)obj;
if(maxScore - score <= 10){
level = 'A';
}else if(maxScore - score <= 20){
level = 'B';
}else if(maxScore - score <= 30){
level = 'C';
}else{
level = 'D';
}
System.out.println("student-" + i + " score is " + score + ",level is " + level);
}
}
}
native 关键的理解
使用 native 关键字说明这个方法是原生函数,也就是这个方法是用 C/C++等非Java 语言实现的,并且被编译成了 DLL,由 java 去调用。
(1)为什么要用 native 方法:java 使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了。例如:有时 java 应用需要与 java 外面的环境交互。这是本地方法存在的主要原因,你可以想想 java 需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:
它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁琐的细节。
(2)native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用一个 native method 方法可以返回任何 java 类型,包括非基本类型,而且同样可以进行异常控制。native method 的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。JVM 将控制调用本地方法的所有细节。如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用 java语言重写这个方法(如果需要的话)。
6-1 关键字:static
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中
都单独分配一个用于代表国家名称的变量。
class Circle{
private double radius;
public Circle(double radius){this.radius=radius;}
public double findArea(){return Math.PIradiusradius;}}
创建两个Circle对象
Circle c1=new Circle(2.0); //c1.radius=2.0
Circle c2=new Circle(3.0); //c2.radius=3.0
Circle类中的变量radius是一个实例变量(instance variable),它属于类的每一个对象,不能被同一个类的不同对象所共享。
上例中c1的radius独立于c2的radius,存储在不同的空间。c1中的radius变化不会影响c2的radius,反之亦然。
如果想让一个类的所有实例共享数据,就用类变量!
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
类变量(class Variable)
类变量(类属性)由该类的所有实例共享
static使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点:
随着类的加载而加载
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法。
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;}
public Person() {
total++;
id = total;
}}
public class PersonTest {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println( "Number of total is "+ Person.getTotalPerson());
}}
因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也不能有super ? YES!)
static修饰的方法不能被重写
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.total=total; //非法,在static方法中不能有this,也不能有super
}
public Person() {
total++;
id = total;
}}
public class PersonTest {
public static void main(String[] args) {
Person.setTotalPerson(3);
} }
package com.atguigu.java1;
/*
* static关键字的使用
*
* 1.static:静态的
* 2.static可以用来修饰:属性、方法、代码块、内部类
*
* 3.使用static修饰属性:静态变量(或类变量)
* 3.1 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
* 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的
* 非静态属性时,不会导致其他对象中同样的属性值的修改。
* 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致
* 其他对象调用此静态变量时,是修改过了的。
* 3.2 static修饰属性的其他说明:
* ① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
* ② 静态变量的加载要早于对象的创建。
* ③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
*
* ④ 类变量 实例变量
* 类 yes no
* 对象 yes yes
*
* 3.3 静态属性举例:System.out; Math.PI;
*
* 4.使用static修饰方法:静态方法
* ① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
* ② 静态方法 非静态方法
* 类 yes no
* 对象 yes yes
* ③ 静态方法中,只能调用静态的方法或属性
* 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
*
* 5. static注意点:
* 5.1 在静态的方法内,不能使用this关键字、super关键字
* 5.2 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
*
* 6. 开发中,如何确定一个属性是否要声明为static的?
* > 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
* > 类中的常量也常常声明为static
*
* 开发中,如何确定一个方法是否要声明为static的?
* > 操作静态属性的方法,通常设置为static的
* > 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
*/
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA";
System.out.println(c1.nation);
//编译不通过
// Chinese.name = "张继科";
c1.eat();
Chinese.show();
//编译不通过
// Chinese.eat();
// Chinese.info();
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃中餐");
//调用非静态结构
this.info();
System.out.println("name :" +name);
//调用静态结构
walk();
System.out.println("nation : " + nation);
}
public static void show(){
System.out.println("我是一个中国人!");
//不能调用非静态的结构
// eat();
// name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);
walk();
}
public void info(){
System.out.println("name :" + name +",age : " + age);
}
public static void walk(){
}
}
static关键字例程:
//static关键字的应用
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle(3.4);
System.out.println("c1的id:" + c1.getId() );
System.out.println("c2的id:" + c2.getId() );
System.out.println("c3的id:" + c3.getId() );
System.out.println("创建的圆的个数为:" + Circle.getTotal());
}
}
class Circle{
private double radius;
private int id;//自动赋值
public Circle(){
id = init++;
total++;
}
public Circle(double radius){
this();
// id = init++;
// total++;
this.radius = radius;
}
private static int total;//记录创建的圆的个数
private static int init = 1001;//static声明的属性被所有对象所共享
public double findArea(){
return 3.14 * radius * radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
}
static关键字练习:
package com.atguigu.exer;
/*
* 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,
* 定义封装这些属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。
考虑:哪些属性可以设计成static属性。
*/
public class Account {
private int id;
private String pwd = "000000";
private double balance;
private static double interestRate;
private static double minMoney = 1.0;
private static int init = 1001;//用于自动生成id使用的
public Account(){
id = init++;
}
public Account(String pwd,double balance){
id = init++;
this.pwd = pwd;
this.balance = balance;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public static double getMinMoney() {
return minMoney;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
}
}
static应用:单例 (Singleton)设计模式
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”。
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象
的变量也必须定义成静态的。
- 单例(Singleton)设计模式-饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
- 单例(Singleton)设计模式-懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
//注:懒汉式暂时还存在线程安全问题,讲到多线程时,可修复
- 单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
举例 java.lang.Runtime
package com.atguigu.java2;
/*
* 单例设计模式:
* 1. 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
* 2. 如何实现?
* 饿汉式 vs 懒汉式
* 3. 区分饿汉式 和 懒汉式
* 饿汉式:
* 坏处:对象加载时间过长。
* 好处:饿汉式是线程安全的
* 懒汉式:好处:延迟对象的创建。
* 目前的写法坏处:线程不安全。--->到多线程内容时,再修改
*/
public class SingletonTest1 {
public static void main(String[] args) {
// Bank bank1 = new Bank();
// Bank bank2 = new Bank();
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
package com.atguigu.java2;
/*
* 单例模式的懒汉式实现
*/
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
- 单例(Singleton)设计模式-应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
2.应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
3.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
4.项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
5.Application 也是单例的典型应用
6.Windows的Task Manager (任务管理器)就是很典型的单例模式
7.Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
理解main方法的语法
-
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
-
又因为main()方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
-
命令行参数用法举例
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(“args[” + i + “] = " + args[i]);
}
}
}
//运行程序CommandPara.java
java CommandPara “Tom” “Jerry" “Shkstart"
输出结果:
args[0] = Tom
args[1] = Jerry
args[2] = Shkstart
package com.atguigu.java2;
/*
* main()方法的使用说明:
* 1. main()方法作为程序的入口
* 2. main()方法也是一个普通的静态方法
* 3. main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
*/
public class MainTest {
public static void main(String[] args) {//入口
Main.main(new String[100]);
MainTest test = new MainTest();
test.show();
}
public void show(){
}
}
class Main{
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
6-3 类的成员之四:代码块
代码块(或初始化块)的作用:
对Java类或对象进行初始化
代码块(或初始化块)的分类:
一个类中代码块若有修饰符,则只能被static修饰(也可以不用任何修饰),称为静态代码块(static block),没有使用static修饰的,为非静态代码块。
static代码块通常用于初始化static的属性。
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
package com.atguigu.java3;
/*
* 类的成员之四:代码块(或初始化块)
*
* 1. 代码块的作用:用来初始化类、对象
* 2. 代码块如果有修饰的话,只能使用static.
* 3. 分类:静态代码块 vs 非静态代码块
*
* 4. 静态代码块
* >内部可以有输出语句
* >随着类的加载而执行,而且只执行一次
* >作用:初始化类的信息
* >如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
* >静态代码块的执行要优先于非静态代码块的执行
* >静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
*
* 5. 非静态代码块
* >内部可以有输出语句
* >随着对象的创建而执行
* >每创建一个对象,就执行一次非静态代码块
* >作用:可以在创建对象时,对对象的属性等进行初始化
* >如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
* >非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
*
*/
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person{
//属性
String name;
int age;
static String desc = "我是一个人";
//构造器
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代码块
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
//调用非静态结构
age = 1;
eat();
//调用静态结构
desc = "我是一个爱学习的人1";
info();
}
//static的代码块
static{
System.out.println("hello,static block-2");
}
static{
System.out.println("hello,static block-1");
//调用静态结构
desc = "我是一个爱学习的人";
info();
//不可以调用非静态结构
// eat();
// name = "Tom";
}
//方法
public void eat(){
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info(){
System.out.println("我是一个快乐的人!");
}
}
总结:程序中成员变量赋值的执行顺序
一、声明成员变量的默认初始化
二、显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
三、构造器再对成员进行初始化操作
四、通过”对象.属性”或”对象.方法”的方式,可多次给属性赋值
6.4 关键字:final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
final标记的类不能被继承。提高安全性,提高程序的可读性。如:String类、System类、StringBuffer类都被final修饰。
final标记的方法不能被子类重写。比如:Object类中的getClass()。
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。如final double MY_PI = 3.14;
- final修饰类(类不能被继承)
final class A{
}
class B extends A{ //错误,不能被继承。
}
- final修饰方法(方法不能被重写)
class A {
public final void print() {
System.out.println(“A”);
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println(“加油”);
}
}
3 final修饰变量——常量
常量名要大写,内容不可修改。
static final:全局常量
class A {
private final String INFO = “atguigu”; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = “尚硅谷”;
}
}
4.final应用举例:
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J;
J = 20;
J = 30; // 非法
}
}
package com.atguigu.java3;
/*
* final:最终的
* 1. final可以用来修饰的结构:类、方法、变量
* 2. final 用来修饰一个类:此类不能被其他类所继承。
* 比如:String类、System类、StringBuffer类
* 3. final 用来修饰方法:表明此方法不可以被重写
* 比如:Object类中getClass();
* 4. final 用来修饰变量:此时的"变量"就称为是一个常量
* 4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
* 4.2 final修饰局部变量:
* 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值
* 以后,就只能在方法体内使用此形参,但不能进行重新赋值。
* static final 用来修饰属性:全局常量
*/
public class FinalTest {
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
// final int DOWN;
{
LEFT = 1;
}
public FinalTest(){
RIGHT = 2;
}
public FinalTest(int n){
RIGHT = n;
}
// public void setDown(int down){
// this.DOWN = down;
// }
public void doWidth(){
// width = 20;
}
public void show(){
final int NUM = 10;//常量
// NUM += 20;
}
public void show(final int num){
// num = 20;//编译不通过
System.out.println(num);
}
public static void main(String[] args) {
int num = 10;
num = num + 5;
FinalTest test = new FinalTest();
// test.setDown(3);
test.show(10);
}
}
final class FinalA{
}
//class B extends FinalA{
//
//}
//class C extends String{
//
//}
class AA{
public final void show(){
}
}
class BB extends AA{
// public void show(){
//
// }
}
6-5 抽象类与抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
1.用abstract关键字来修饰一个类,这个类叫做抽象类。用abstract来修饰一个方法,该方法叫做抽象方法。抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:public abstract void talk();
2.含有抽象方法的类必须被声明为抽象类。抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
3.abstract使用上的注意点:不能用abstract修饰变量、代码块、构造器;不能用abstract修饰私有方法、静态方法、final的方法、final的类。
- 抽象类应用:抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。抽象类举例:
abstract class void A {
m1();
public void m2() {
System.out.println(“A类中定义的m2方法”);
}
}
class B extends A {
void m1() {
System.out.println(“B类中定义的m1方法”);
}
}
public class Test {
public static void main(String args[]) {
A a = new B();
a.m1();
a.m2();
}
}
package com.atguigu.java;
/*
* abstract关键字的使用
* 1.abstract:抽象的
* 2.abstract可以用来修饰的结构:类、方法
*
* 3. abstract修饰类:抽象类
* > 此类不能实例化
* > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
* > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
* 4. abstract修饰方法:抽象方法
* > 抽象方法只有方法的声明,没有方法体
* > 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
* > 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
* 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
*/
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
// p1.eat();
}
}
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法:
// public void eat(){
//
// }
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
abstract的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println(“执行时间是:” + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
数据库访问的封装
Junit单元测试
JavaWeb的Servlet中关于doGet/doPost方法调用
Hibernate中模板程序
Spring中JDBCTemlate、HibernateTemplate等
6-6 接口(interface)
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
接口(interface)是抽象方法和常量值定义的集合。
接口的特点:
用interface来定义。
接口中的所有成员变量都默认是由public static final修饰的。
接口中的所有抽象方法都默认是由public abstract修饰的。
接口中没有构造器。
接口采用多继承机制。
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements Interface A{ }
一个类可以实现多个接口,接口也可以继承其它接口。
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
接口的主要用途就是被实现类实现。(面向接口编程)。
与继承关系类似,接口与实现类之间存在多态性
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
接口定义举例:
接口定义举例一:
public interface Runner {
public static final int ID = 1;
public abstract void start();
public abstract void run();
public abstract void stop();
}
interface Runner {
public void start();
public void run();
public void stop();
}
class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼
// 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
}
}
package com.atguigu.java1;
/*
* 接口的使用
* 1.接口使用interface来定义
* 2.Java中,接口和类是并列的两个结构
* 3.如何定义接口:定义接口中的成员
*
* 3.1 JDK7及以前:只能定义全局常量和抽象方法
* >全局常量:public static final的.但是书写时,可以省略不写
* >抽象方法:public abstract的
*
* 3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
*
* 4. 接口中不能定义构造器的!意味着接口不可以实例化
*
* 5. Java开发中,接口通过让类去实现(implements)的方式来使用.
* 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
* 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
*
* 6. Java类可以实现多个接口 --->弥补了Java单继承性的局限性
* 格式:class AA extends BB implements CC,DD,EE
*
* 7. 接口与接口之间可以继承,而且可以多继承
* *******************************
* 8. 接口的具体使用,体现多态性
* 9. 接口,实际上可以看做是一种规范
*
* 面试题:抽象类与接口有哪些异同?
*
*/
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
//Interfaces cannot have constructors
// public Flyable(){
//
// }
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void method1() {
// TODO Auto-generated method stub
}
@Override
public void method2() {
// TODO Auto-generated method stub
}
}
//************************************
// 接口与接口之间可以继承,而且可以多继承
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
接口和抽象类之间的对比
区别点 | 抽象类 | 接口 |
---|---|---|
定义 | 包含抽象方法的类 | 主要是抽象方法和全局常量的集合 |
组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法、(jdk8.0:默认方法、静态方法) |
使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
常见设计模式 | 模板方法 | 简单工厂、工厂方法、代理模式 |
对象 | 都通过对象的多态性产生实例化对象 | 都通过对象的多态性产生实例化对象 |
局限 | 抽象类有单继承的局限 | 接口没有此局限 |
实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 |