面向对象
- 类是对一类事物的描述 ,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例 (instance)
面向对象的三大特征
- 封装 (Encapsulation)
- 继承 (Inheritance)
- 多态 (Polymorphism)
万事万物皆对象
- 在java语言范畴中,将功能、结构等封装到类中,通过实例化调用具体的功能结构
- 涉及到java语言与前端HTML、后端的额数据库交互时,前后端的结构在java层面交互时,都提现为类、对象。
Java类及类的成员
也可以不定义对象的句柄,而直接调用这个对象的方法。这
样的对象叫做匿名对象。如: new Person().shout(); 如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。我们经常将匿名对象作为实参传递给一个 方法。
类中属性
- 语法格式:修饰符
数据类型 属性名 = 初始化值 ; - 说明1:修饰符:常用的权限修饰符有: private 、缺省、 protected 、 public;其他修饰符: static 、 final ( 暂不考虑)
- 说明2:数据类型:任何基本数据类型或任何引用数据类型。
- 说明3:属性名:属于标识符,符合命名规则和规范即可。
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参、代码块、构造器 |
修饰符 | private、public、static、final等 | 可用final |
初始化值 | 有默认初始化值 | 必须显示赋值 |
内存加载位置 | 堆空间 | 栈空间 |
类中方法
Java 里的方法不能独立存在,所有的方法必须定义在类里。
修饰符 返回值类型 方法名 (参数类型 形参1, 参数类型 形参2,...){
方法体程序代码
return 返回值
}
- 没有具体返回值的情况,返回值类型用关键字 void 表示 ,那么方法体中可以不必使用 return 语句。如果使用,”return;”仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理 。
- 方法中只能调用方法或属性,==不可以在方法内部定义方法==。
方法重载
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。与权限修饰符、返回值类型、形参无关。
可变形参
- 声明格式: 方法名 (参数的类型名… 参数名)
- 可变参数:方法参数部分指定类型的参数个数是可变 多 个: 0 个, 1 个或多个
- 可变个数形参的方法与本类中同名,形参不同的方法之间彼此构成重载
- 可变参数方法的使用与本类中同名,形参为数组的方法之间不构成重载,无法共存
- 方法的参数部分有可变形参,需要放在形参声明的最后
- 在 一个方法的形参位置,最多只能声明一个可变个数形参
参数值传递
Java
里方法的参数传递方式只有一种: 值传递 。 即将实际参数值的副本
(复制品)传入方法内,而参数本身不受 影响 。
- 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
- 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参(包括数据类型)
class Value{
int i=15;
}
public class TestArray {
public static void main(String[] args) {
TestArray testArray = new TestArray();
testArray.first();
}
public void first(){
int i =5;
Value v = new Value();
v.i = 25;
second(v, i);
System.out.println(v.i); // 20
}
public void second(Value v , int i ){
i = 0;
v.i=20; // 局部变量指向的内存中类的属性被修改
Value val = new Value();
v=val; // 局部变量指向了新的内存
// ==存的是地址值,改的也是地址值==
System.out.println(v.i+" "+i); // 15 0
}
}
int[] arr1 = new int[]{1,2,3};
char[] arr2 = new char[]{'1','2','3'};
System.out.println(arr1);// [I@1b6d3586
System.out.println(arr2);// 123
// println()重载
三大特性一:封装
把该隐藏的隐藏起来,该暴露的暴露出来 。 这就是封装性的设计思想。
“高内聚,低耦合”:类的内部数据操作细节自己完成,不允许外部干涉;仅对外暴露少量的方法。
封装性的体现:
- 类的属性私有化(private),同时提供公共的(public)的方法获取(getXXX)和设置(setXXX)属性;
- 不对外暴露私有的方法;
- 单例模式;
四种权限修饰符:
从小到大:private—>缺省—>protected—>public
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | yes | |||
缺省 | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
- 4种权限可以用来修饰类的内部结构:属性、方法、构造器、内类;
- 对于class 的权限修饰只可以用 public 和 缺省(default)。
public 类可以在任意地方被访问。
default 类只可以被同一个包内部的类访问。
构造器
构造器的作用:创建对象;给对象进行初始化。
构造器的特点:
- 它具有与类相同的名称
- 它不声明返回值类型。(与声明为 void 不同)
- 不能被 static 、 final 、 synchronized 、 abstract 、 native 修饰,不能有
return 语句返回值。
注 意:
- Java 语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器, 则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
赋值的位置及顺序:
- 默认初始化;int i;
- 显式初始化;int i=9;
- 构造器中初始化;
- 通过“对象 属性“或“对象 方法”的方式赋值;
this、package、import关键字
this
- this可以调用类的属性、方法和构造器 ;
- 它在方法内部使用,即这个方法所属对象的引用;
- 它在构造器内部使用,表示该构造器正在初始化的对象。
- 方法和构造器中形参和属性重名,需使用this区分,不重名则无要求。
class Person{
private int age=99;
public void setAge(int age){
//形参和属性重名,需使用this区分,不重名则无要求。
//形参=形参, 赋值失败
age = age;
//可以赋值
this.age=age;
}
}
- this 可以作为一个 类中构造器相互调用的特殊格式
注意:
可以 在类的构造器中使用 “ 形参列表 的方式,调用本类中重载的其他的构造器!
明确 :构造器中不能通过 “形参列表 “的方式调用自身构造器
如果 一个类中声明了 n 个构造器,则最多有 n-1 个构造器中使用了
“ 形参列表“this 形参列表 必须声明在类的构造器的==首行==!
在类的一个构造器中,最多只能声明一个 “形参列表”
class Person{
private int age;
private String name;
public Person(){
System.out.println("新对象实例化");
}
public Person(String name){
this();
this.name = name;
}
public Person(String name, int age){
this(name);
this.age = age;
}
}
- `使用 this 访问属性和方法时,如果在本类中未找到,会从父类中查找
package
- package 语句作为 Java 源文件的第一条语句,指明该文件中定义的类所在的包。若缺省该语句,则指定为无名包 。它的格式为:
package 顶层包名.子包名; - 包对应于文件系统的目录,package语句中,用.来指明包目录的层次;
- 包通常用小写单词标识。通常使用所在公司域名的倒置: com.atguigu.xxx
import
- 为使用定义在不同包中的 Java 类,需用 import 语句来引入 指定包层次下所需要的类或全部类 。 import 语句告诉编译器到哪里去寻找类。
- 语法格式:import 包名.类名
注意:
- 在源文件中使用 import 显式的导入指定包下的类或接口
- 声明在包的声明和类的声明之间。
- 如果需要导入多个类或接口,那么就并列显式多个 import 语句即可
- 举例 :可以使用 java.util.* 的方式,一次性导入 util 包下所有的类或接口。
- 如果导入的类或接口是 java.lang 包下的,或者是当前包下的,则可以省略此 import 语句。
- 如果在代码中使用不同包下的同名的类。那么就需要使用类的==全类名==的方式指明调用的是哪个类。
- 如果已经导入 java.a 包下的类。那么如果需要使用 a 包的子包下的类的话,仍然需要导入。
- import static 组合的使用:调用指定类或接口下的静态的属性或方法。
三大特性二:继承
作用:
- 继承的出现减少了代码冗余,提高了代码的复用性。
- 继承的出现,更有利于功能的扩展。
- 继承的出现让类与类之间产生了关系 ,提供了多态的前提
子类继承了父类,就继承了父类的所有属性和方法。特别的,private属性,由于封装性的影响,子类不能直接调用。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。实现功能的扩展。在 Java 中,继承的关键字用的是“ extends ””,即子类不是父类的子集而是对父类的“扩展” 。
Java 只支持==单继承==和多层继承,不允许多重继承
:一个子类只能有一个父类;一个父类可以派生出多个子类。
方法重写
- 定义 :在子类中可以根据需要对从父类中继承来的方法进行改造也称为方法的重置、覆盖 。在程序执行时,子类的方法将覆盖父类的方法。
- 要求
- 子类重写的方法必须和父类被重写的方法具有相同的方法名称、 参数列表
- 父类被重写的返回值类型为
基本数值类型
时,子类重写方法的返回值类型必须相同;父类被重写的返回值类型为引用数据类型
时(如Object),子类重写方法的返回值类型只能是相同类型或者其子类(String)。 - 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限子类==不能重写==父类中声明为 ==private 权限==的方法
- 子 类方法抛出的异常不能大于父类被重写方法的异常,即异常的子类。
- 注意:子类与父类中同名同参数的方法必须同时声明为非 static 的(即为重写),或者同时声明为 static 的(不是重写) 。因为 static 方法是属于类的,子类无法覆盖父类的方法。
super关键字
super和 this 的用法相像,this 代表本类对象的引用,super 代表父类的内存空间的标识。
- 在子类的方法或者构造器中,通过使用”super.属性”和”super.方法”,显示地调用父类中声明的属性或方法,一般省略super
- 特殊情况:当子父类出现同名属性或方法时可以用 super 表明调用的是父类(父类的父类)中的属性或方法,使用this表明调用的是子类中的属性或方法。
super可以调用父类的构造器。在子类的构造器中显式地使用”super(形参列表)”的方式调用父类中指定的构造器。且必须声明在子类构造器的首行。
- 子类中所有的构造器默认都会访问父类中空参数的构造器,在类的多个构造器汇总,至少有一个调用了”super(形参列表)”.
- 当父类中没有空参数的构造器时,子类的构造器必须通过 this(参数列表) 或者 super(参数列表) 语句指定调用本类或者父类中相应的构造器 。 同时==只能二选一,且必须放在构造器的首行==。
- 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错。
明确:虽然创建子类时,调用了父类的构造器,但是自始至终就创建过一个对象,即new的对象。
三大特性三:多态
使用前提:1. 类的继承关系;2. 方法重写
对象的多态性:父类的引用指向子类的对象
多态的使用:虚拟方法调用
在编译器,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写的父类方法,不能调用子类特有的属性和方法。==编译看左边,运行看右边==。
多态性不适用属性!!!子父类中同名的属性,多态时调用父类的属性。
小结:方法的重载与重写
- 二者的定义细节: 略
- 从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同
。 编译器根据方法不
同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成 了不同的方法。 它们的调用地址在编译期就绑定了 。 Java 的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:
对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,
这称为 “早绑定”或“静态绑定。而对于多态,只有等到方法调用的那一刻 解释运行器才会确定所要调用的具体方法,这称为 “晚绑定”或“动态绑定 。 - 重载不表现为多态性,重写表现为多态性。
instanceof 、Casting 操作符
instanceof
有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。
如何才能调用子类特有的属性和方法?答:使用强制类型转换!但可能会出现异常。
Persion p = new Man();
Man m = (Man)p; // 可以调用Man特有的属性和方法
Woman m = (Woman)p; // 出现异常ClassCastException
a instanceof A : 判断对象a是否为类A的实例:如果是,返回true,否则,返回false。
// 为了避免在向下转型时出现ClassCastException异常,所以需要先判断类型
// 编译通过,运行不过
Person p = new Woman();
Man m = (Man)p;
Person p = new Person();
Man m = (Man)p;
// 编译通过,运行通过
Object obj = new Woman();
Person p = (Person)obj;
// 编译不通过
Man m = new Woman();
class Base {
int count = 10;
public void display() {
System.out.println(this.count);
}
}
class Sub extends Base {
int count = 20;
public void display() {
System.out.println(this.count);
}
}
public class FieldMethodTest {
public static void main(String[] args){
Sub s = new Sub();
System.out.println(s.count);//20
s.display();//20
Base b = s;//多态
// == 对于引用数据类型,比较的是两个引用数据类型的地址是否相等
System.out.println(b == s);// true
System.out.println(b.count);// 10
b.display();//20
}
}
面试题:多态是编译时行为还是运行时行为?
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);
animal.eat();
}
}
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3); // sub_1, 多态
Sub s = (Sub)base;
s.add(1,2,3);// sub_2
}
}
class Base {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
Object 类
- Object 类是所有 Java 类的根父类
- 如果在类的声明中未使用 extends 关键字指明其父类,则默认父类为 java.lang.Object 类
- 数组也作为object类的子类出现,可以调用object中的方法
==操作符与 equals 方法
==:
- 基本类型比较值:只要两个变量的值相等,即为 true 。
- 引用类型比较引用 (是否指向同一个 对象):只有指向同一个对象时,才返回 true
用==进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
equals():
是一个方法,而非运算符。只适用于引用数据类型
默认equals():object类中的equals()的定义:和 == 一致、
public boolean equals(Object obj)}{ return (this == obj); }
特例 :当用 equals() 方法进行比较时对类 File 、 String 、 Date 及包装类Wrapper Class 来说是比较类型及内容而不考虑引用的是否是同一个对象
当自定义使用 equals() 时,可以重写。用于比较两个对象的内容是否都相等。重写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 。
先用==比较地址是否相同,然后再比较属性值是否全部相同。
面试题:== 和 equals 的区别
1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型,就是比较内存地址
2 equals 的话,它是属于 java.lang.Object 类里面的方法,如果该方法没有被重写过默认也是==,我们可以看到 String 等类的 equals 方法是被重写过的,而且 String 类在日常开发中用的比较多,久而久之,形成了 equals 是比较值的错误观点。
3 具体要看自定义类里有没有重写 Object 的 equals 方法来判断。
4 通常情况下,重写 equals 方法,会比较类中的相应属性是否都相等。
toString() 方法
输出一个对象的引用时,实际上是调用当前对象的toString()。”System.out.println(ClassA);”
Object类中的toString()方法:返回类名和它的引用地址
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
String、Data、File、包装类等都重写了Object类中的toString()方法,使得在调用toString方法时,返回“实体内容”信息。进行 String 与其它类型数据的连接操作时,自动调用 toString() 方法
包装类 (Wrapper)和单元测试
JUnit单元测试:
- add libraries —- JUnit 4
- 创建Java类,进行单元测试:1)Java类是public的,2)此类提供公共的无参构造器
- 此类中声明单元测试方法:public,void,无形参。
- 单元测试方法上需要声明注解:@Test,需要导入包:import org.junit.Test
- 声明好单元测试方法后,在方法体内测试相关代码
包装类
- 针对八种基本数据类型定义 相应的引用类型包装类(封装类)
- 有了类的特点,就可以调用类中的方法, Java才是真正的面向对象
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Charactor |
基本数据类型转化为包装类:调用构造器即可,可传入数字或字符串。注意,Boolean类型,传入的字符小写化后为“true”即为true,如“true123”为false。
包装类转化为基本数据类型:调用xxxValue()方法
JDK1.5 之后,支持自动装箱,自动拆箱。但类型必须匹配。
int num = 10; Integer num1 = num; int num2 num1;
- 基本数据类型和包装类转换到String类型:均可以调用String重载的valueOfxxx();
面试题:
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0 :前后要求类型一致
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);//1
public void method() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);// false 比较地址值
Integer m = 1;
Integer n = 2;
System.out.println(m == n);// true
// Integer内部类IntegerCache中定义了Integer[],保存了-128 -- +127
// 如果使用自动装箱的方法,给Integer赋值的访问在-128 -- +127范围内时,可以直接使用数组中的元素,不需要new, 即地址值一样
Integer x = 128;
Integer y = 128;
System.out.prinltn(x == y);// false new的对象,地址值不一样
}