Java 面向对象
类和对象
- 类: 按照功能来划分
- 工具类: 这种类 里面 包含了大量的方法 每个方法里面都有明确的功能
- 测试类 :能够运行的类
- JavaBean类 :这种类里面 几乎全部都是属性, 每个属性都有private 并且 setget方法。这种类 是用来封装数据用的。
类是一个模具,而对象是模具做出来的东西。
- 类:对一类事物的描述 模板 设计图…
- 成员变量:对应事物的属性
- 成员方法:对应事物的行为
// 属性 : 成员变量 (类中方法外面)就是该事物的状态信息。
// 行为 :就是该事物能够做什么,行为用方法来表示。
举例:小猫。
属性:名字、体重、年龄、颜色。
行为:走、跑、叫。
- 对象:是一类事物的具体体现。对象是类的一个实例 ,必然具备该类事物的属性 和行为。
现实中,一类事物的一个实例:一只小猫。
举例:一只小猫。
属性:tom、5kg、2 years、yellow。
行为:溜墙根走、蹦跶的跑、喵喵叫。
- 类与对象的关系:
- 类是对一类事物的描述,是抽象的。
- 对象是一类事物的实例,是具体的。
- 类是对象的模板,对象是类的实体。
类是对象的描述
对象是类的实体
根据一个类我可以创建多个对象
类的定义格式
public class ClassName {
//成员变量
//成员方法
}
- 定义类:就是定义类的成员,包括成员变量和成员方法。
- 成员变量:和以前定义变量几乎是一样的。只不过位置发生了改变。在类中,方法外。
- 成员方法:和以前定义方法几乎是一样的。只不过把static去掉。
类的组成
- 属性:在代码中通过成员变量来体现(类中方法外)
- 行为:在代码中通过成员方法来体现(和前面的方法相比去掉static即可)
对象的使用
- 创建对象:类名 对象名 = new 类名();
- 使用对象访问类中的成员:
- 对象名.成员变量;
- 对象名.成员方法();
内存图
方法区 是用来存储 类的 字节码文件的, 就是代码的存储位置。
堆内存 用来存储 对象的数据的。
栈内存 代码一句一句的执行 在栈里面发生的, 栈里面是以方法为单位。
类的代码存储在 方法区中的。
对象 是创建在 堆内存中的, 我们给对象中的属性赋值, 是赋值到堆内存中的。
- 类的字节码文件, 只有第一次使用到的时候, 加载一次, 以后再执行到的时候 就不再加载了。
- 类的字节码文件在 方法区的存储,是跟着jvm的, 只要jvm不停止, 方法区里面的东西就不会清楚。
- 引用类型的数据 每new一次 就是要在堆内存开辟新的空间。
- 可以多个变量同时去操作 一个对象(堆内存中的), 只要其中一个通过变量去变量 对象里面的内容,第二变量再去使用对象里面的内容的时候 就已经发生改变了。
- 当一个应用类型变量 不指向堆内存中任何内容了, 这时候再通过这个变量去访问内容 就会报空指针异常
- 引用类型作为参数,传递的是地址值。
垃圾回收
- 栈内存:方法被调用 就会在栈内存存在,
- 方法执行结束之后,从栈内存中消失了,包括里面的 变量 以及基本类型的数据值 都消失了。
- 堆内存:当指向这个堆内存中的对象的 所有的变量 都消失了的时候。
- 这时候 这个对象 就会变成内存垃圾,但是并不会立刻消失。 而是 java底层有一个垃圾回收机制 定时轮训 每隔一段时间就去检测垃圾,并且回收垃圾。
- 方法区:方法区中的内容, 只要jvm不关闭,任何时候都不消失。
注意:当堆内存中,对象或者数组产生的地址,通过任何方式都不能被找到之后,就会被内存判定为垃圾,垃圾会被java垃圾回收器空闲的时候自动进行清理。
成员变量和局部变量的区别
类: 按照功能来划分
- 工具类: 这种类 里面 包含了大量的方法 每个方法里面都有明确的功能,这种类里面通常没有属性。
class Uitls {
public void show(){
}
public void method(){
}
....
}
- 测试类 :能够运行的类,含有main方法。
class Demo {
public static void main(String[] args){
}
}
class Demo1 {
@Test
public void show(){
}
}
- JavaBean类 :这种类里面 几乎全部都是属性, 每个属性都有private 并且 setget方法。这种类 是用来封装数据用的。
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Demo {
public static void main(String[] args){
//数组
//int[] arr = new int[]{10,20,30};
int[] arr = new int[2];
arr[0] = 10;
arr[1] = 20;
//Student stu = new Student(13,"zhangsan");
Student stu = new Student();
stu.setAge(13);
stu.setName("张三");
}
}
方法
方法:就是将一个功能抽取出来,把代码单独定义在一个大括号内,形成一个单独的功能。
当我们需要这个功能的时候,就可以去调用。这样即实现了代码的复用性,也解决了代码冗余的现象。
- 方法作用: 是用来降低重复度的。
- 循环也是用来降低重复度的,那他俩有什么区别呢?
循环只能提高局部代码的复用性, 但是方法可以提高全局代码的复用性。
方法命名规则
方法名:首字母小写和驼峰原则 例:run(),runRun()
方法的定义
- 格式一:无参数、无返回值
public static void 方法名(){
语句;
}
public static 写死的。
void 方法没有返回值。
- 格式二:有参数、无返回值
public static void 方法名(数据类型 变量名,数据类型 变量名,数据类型 变量名,.... ){
方法体;//很多条语句
}
- 格式三:有参数、有返回值
public static 数据类型 方法名(数据类型 变量名,数据类型 变量名,数据类型 变量名,.... ){
方法体;//很多条语句
return 返回值;
}
- 方法的完整格式:
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
修饰符 返回值类型 方法名(参数类型 参数名){
...
方法体
...
return 返回值;
}
方法包含一个方法头和一个方法体。下面是一个方法的所有部分:
-
修饰符:修饰符,这是可选的,告诉编译器如何调用该方法。定义了该方法的访问类型。
-
返回值类型 :方法可能会返回值。returnValueType 是方法返回值的数据类型。有些方法执行所需 的操作,但没有返回值。在这种情况下,returnValueType 是关键字void。
-
方法名:是方法的实际名称。方法名和参数表共同构成方法签名。
-
参数类型:参数像是一个占位符。当方法被调用时,传递值给参数。这个值被称为实参或变量。参
数列表是指方法的参数类型、顺序和参数的个数。参数是可选的,方法可以不包含任何参数。 -
- 形式参数:在方法被调用时用于接收外界输入的数据。
- 实参:调用方法时实际传给方法的数据。
-
方法体:方法体包含具体的语句,定义该方法的功能。
方法定义的注意事项:
- main方法调用另外一个方法, 等待着另一个方法执行结束之后,main才能继续
方法的注意事项:
- 方法不能嵌套
- 方法和方法之间是没有先后顺序的。
- 方法不调用不会执行的, 因为我们程序的入口都是main方法。
- void的方法,说明方法内是不能有返回值的, 但是你是可以写一个 单独的return的。用来强制终止方法。
-
return 这个关键字的作用, 两个作用
-
- 第一个:return 强制终止方法的作用,return 后面的语句永远执行不到。
- 第二个: return 返回值, 把 值扔给调用者
- 如果一个方法有返回值类型, 那么这个方法 则必须各个逻辑上,都应该有返回值。
-
- 返回值类型,必须要和 return 语句返回的类型相同,否则编译失败 。
- 不能在 return 后面写代码, return 意味着方法结束,所有后面的代码永远不会执行,属于无效代码。
方法调用
方法必须先定义后调用,否则程序将会报错
方法在定义完毕后,方法不会自己运行,必须被调用才能执行,我们可以在主方法main中来调用我们自己定义好的方法。
- 直接调用:直接写方法名调用
public static void main(String[] args) {
print(); //调用方法
}
- 赋值调用:调用方法,在方法前面定义变量,接收方法返回值
public static void main(String[] args) {
int sum = getSum(5,6); //调用方法
System.out.println(sum);
}
public static int getSum(int a,int b) {
return a + b;
}
3.输出语句调用: 在输出语句中调用方法, System.out.println(方法名()) 。
- 不能用输出语句调用 void 类型的方法。因为方法执行后没有结果,也就打印不出任何内容。
方法调用过程
- 方法没有被调用的时候,都在方法区中的字节码文件(.class)中保存
- 方法被调用的时候,需要进入到栈内存中运行
形参和实参
- 形参:形式参数,是指方法定义中的参数
- 实参:实际参数,方法调用中的参数
方法的重载
- 概念: 同一个类中 出现了方法名相同 参数列表必须不同, 返回值类型无关,这些方法相互重载。
- 作用: 有了重载之后, 方便程序员的记忆。 减少了负担。
方法重写
- 重写 和重载的区别:
重写: 子父类关系中,子类出现了 方法声明 和父类的方法 一模一样的方法 , 就是方法的重写。
重载: 在同一个类中 方法名相同 参数列表不同 返回值类型无关
- Override注解
- 用来检测当前的方法,是否是重写的方法,起到【校验】的作用
方法重写的注意事项
- 私有的方法不能重写, (父类私有成员子类是不能继承的)
是因为 子类访问不了父类的私有方法 ,访问不了 也就重写不到。
并不是 只有父类私有的子类访问不了, 父类其他权限修饰符的 的一些方法 子类也有可能访问不到。
- 静态的方法 不能被重写
- 子类重写父类方法权限修饰符大于等于父类方法的权限修饰符
- 静态方法不能被重写,如果子类也有相同的方法,并不是重写的父类的方法
示例代码
public class Fu {
private void show() {
System.out.println("Fu中show()方法被调用");
}
void method() {
System.out.println("Fu中method()方法被调用");
}
}
public class Zi extends Fu {
/* 编译【出错】,子类不能重写父类私有的方法*/
@Override
private void show() {
System.out.println("Zi中show()方法被调用");
}
/* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
private void method() {
System.out.println("Zi中method()方法被调用");
}
/* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
public void method() {
System.out.println("Zi中method()方法被调用");
}
}
历史遗留问题:
方法的重写:
1:私有的不算重写
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
//f.show(); // 报错 说明 不是重写
//show 是私有的 只能在 class fu 里面去用的。
f.method();
}
}
class Fu{
private void show(){
System.out.println("fu");
}
public void method(){
}
}
class Zi extends Fu{
public void show(){
System.out.println("zi");
}
}
2:静态的也不算重写
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
f.show(); //fu
}
}
class Fu{
public static void show(){
System.out.println("fu");
}
}
class Zi extends Fu{
public static void show(){
System.out.println("zi");
}
}
3. 之前我们总说 重写父类的方法 方法声明必须一模一样。 其实返回值可以不一样。
这个不一样,也是有关系的
子类方法的返回值类型 必须 是 父类方法返回值类型的子类。
public class demo1 {
public static void main(String[] args) {
Fu f = new Zi();
Person show = f.show();
System.out.println(show); //test.day04.demo1.Student@f5f2bb7
}
}
class Fu{
public Person show(){
//System.out.println("fu");
return new Person();
}
}
class Zi extends Fu{
public Student show(){
//System.out.println("zi");
return new Student();
}
}
class Person{}
class Student extends Person{}
构造方法
- 定义:用来创建对象 所必需要执行的方法。
- 定义格式:
- 方法名字 必须和类名相同,大小写也要一致。
- 构造方法 没有返回值类型的 void也不能有
- 构造方法必须 只能用 new来调用。
- 不能写返回值,没有具体的返回值 (不能由return带回具体的结果,但是可以单独写一个 return , 目的是为了强制终止构造方法)
class Student {
public Student(){ // 这就是Student类的一个构造方法。
}
}
构造方法的调用
- 构造方法的作用是创建对象, 只能使用new 调用, 用来创建对象。不能使用对象去调用。
class Student{
public Student(){
System.out.println("构造方法执行");
}
}
class Demo {
public static void main(String[] args){
Studnet s = new Student(); // 构造方法执行
Student s1 = new Student(); // 构造方法执行
//s.Student(); // 编译报错
}
}
//-----------------------------------------
class Phone{
public Phone(){
System.out.println("Phone构造方法执行");
}
public void Phone( ){ // 这个方法 就相当于 一个 普通类似举例那种 show方法。 只不过名字 我不叫show了 叫Phone
System.out.println("这是一个普通方法 名字叫Phone(恰好方法名和类名相同了 你管得着吗) 不是构造方法");
}
public void show(){
System.out.println("这是一个普通方法 名字叫show 不是构造方法");
}
}
class Demo {
public static void main(String[] args){
Phone s = new Phone(); // Phone构造方法执行
Phone s1 = new Phone(); // Phone构造方法执行
s.Phone(); // 可以 正确 但是 Phone() 这不是构造方法了。
s.show();
}
}
构造方法的执行时机
创建对象的时候调用,每创建一次对象,都会执行一次构造方法。
不能手动的调用构造方法。
构造方法的作用
- 用于给对象的数据(属性)进行初始化。
构造方法的注意事项
- 1:构造方法 顾名思义 “构造” 也就是说 这个方法是为了创建对象而存在的,换句话说, 如果一个类中没有构造方法 那么这个类创建不了对象。
构造方法的创建:
如果没有定义构造方法,系统将会给出一个默认的无参构造参数。
如果定义了构造方法,系统将不再提供默认的构造方法。
public class Student {
/*
如果一个类中 没有手动写出构造方法
那么系统 会提供给你一个 构造方法
public Student(){
}
手动写出构造方法之后
系统就不再提供了
*/
public Student(){
System.out.println("sdfsdf");
}
}
public class Demo {
public static void main(String[] args) {
Student s = new Student();
// 然而 这个类却能创建对象
// 说明这个类中有构造方法。
System.out.println(s);
}
}
- 2: 构造方法可以重载
如果自定义了带参数的构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法。
无论之后是否使用,都要手动书写无参构造方法和有参数的构造方法。
public class Phone {
/*
构造方法 是不是属于方法。
方法的重载
构造方法也必然存在着重载这种现象
方法名名相同 参数列表必须不同
*/
/*public Phone(){
System.out.println("没有参数的构造方法--无参构造");
}*/
public Phone(int a){
System.out.println("有参数的构造方法--有参构造");
}
/*
如果我们没有手动写出构造方法
系统将会默认提供一个 无参数的构造方法
public Phone(){ }
如果我要手动给出了 系统就不再提供了。
*/
}
public class Demo1 {
public static void main(String[] args) {
//Phone p = new Phone(); //编译报错
Phone p1 = new Phone(10);
}
}
- 3: 构造方法作用, 在创建对象的时候 顺便给 属性赋值
public class Car {
private String brand;
private int price;
public Car(){
}
public Car(String brand, int price){
this.brand = brand;
this.price = price;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void setPrice(int price) {
this.price = price;
}
public int getPrice() {
return price;
}
public String getBrand() {
return brand;
}
}
public class Demo2 {
public static void main(String[] args) {
// 构造方法是创建对象而生的。
// 系统给一个 我也能创建对象, 你给了也能用你的 , 有什么区别吗?
Car c = new Car();
c.setBrand("宝驴");
c.setPrice(199);
//这就是我们创建一个 汽车对象 并把他的属性 弄好的。
// 三句话。 你用了三条语句。
// 我觉得三条语句有点麻烦,
//以后万一 他有1万个属性呢 你需要写1万01条语句
// 那能不能 一条语句结束呢
// 无论你想用几条语句 你必须得有创建对象的那条语句。
// 那我就在想 能不能在创建对象的时候 就给我赋值呢??
Car c1 = new Car("奔马",299);
System.out.println(c1.getBrand());
System.out.println(c1.getPrice());
// 你有了 有参构造之后 你还要 set方法干嘛啊?
// 反正 构造方法可以赋值。
// 你不想要set了
// 你要充分了解一下 set方法的作用。
//1:赋值
// 2: 需求: 刚才那个 奔马 这个名字 我好像起错了 应该是 奔驹
// 假设此时 没有set方法。
//c1 = new Car("奔驹",299); // 你要这么做 就是把之前的那个车 报废掉。 重新造一辆车 九尾了改个名字
//set方法
c1.setBrand("奔驹");
}
}
构造方法
子类在创建对象的时候,必须去调用父类的构造方法(语法规定),为什么语法这个规定呢?
是因为:子类创建对象要使用的时候,保不准就会用到父类的内容, 所以 一定是先去把父类在内存中初始化,那么如何把父类在内存中初始化么,
就是访问父类的构造方法,这就是在内存中初始化。 父类初始化完了之后,子类再继续初始化子类的。
所以 构造方法的访问特点有如下几个:
1: 子类的所有构造方法的第一行 都默认有一个 super();(只不过你看不到而已) 用于访问父类的无参构造。
2: 而且当你显示给出 super(), super(参数),, 系统就不再默认提供 super()
3: 访问构造方法的语句 必须放在 构造方法的第一行。
4: 当父类没有无参构造方法的时候, 你必须要做 子类初始化之前先初始化 父类这一点,
访问 父类的其他有参构造。
访问 子类其他的构造方法。 无论哪一种, 你只需要做到 子类初始化之前 必须先初始化父类就可以。
5: 子类初始化之前,必须先初始化父类,初始化几次 一次(必须一次),所以 this() 和super() 都必须放在第一行,所以他俩不能共存。
以上细节:你只需要记住原理,就不会忘。 子类初始化之前必须先初始化父类,而且必须是初始化一次。
2.7 方法重写的注意事项
1. 私有的方法不能重写, (父类私有成员子类是不能继承的)
是因为 子类访问不了父类的私有方法 ,访问不了 也就重写不到。
并不是 只有父类私有的子类访问不了, 父类其他权限修饰符的 的一些方法 子类也有可能访问不到。
2. 静态的方法 不能被重写
3. 子类重写父类方法权限修饰符大于等于父类方法的权限修饰符
- 静态方法不能被重写,如果子类也有相同的方法,并不是重写的父类的方法
示例代码
public class Fu {
private void show() {
System.out.println("Fu中show()方法被调用");
}
void method() {
System.out.println("Fu中method()方法被调用");
}
}
public class Zi extends Fu {
/* 编译【出错】,子类不能重写父类私有的方法*/
@Override
private void show() {
System.out.println("Zi中show()方法被调用");
}
/* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
private void method() {
System.out.println("Zi中method()方法被调用");
}
/* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
public void method() {
System.out.println("Zi中method()方法被调用");
}
}
构造方法
子类在创建对象的时候,必须去调用父类的构造方法(语法规定),为什么语法这个规定呢?
是因为:子类创建对象要使用的时候,保不准就会用到父类的内容, 所以 一定是先去把父类在内存中初始化,那么如何把父类在内存中初始化么,
就是访问父类的构造方法,这就是在内存中初始化。 父类初始化完了之后,子类再继续初始化子类的。
所以 构造方法的访问特点有如下几个:
1: 子类的所有构造方法的第一行 都默认有一个 super();(只不过你看不到而已) 用于访问父类的无参构造。
2: 而且当你显示给出 super(), super(参数),, 系统就不再默认提供 super()
3: 访问构造方法的语句 必须放在 构造方法的第一行。
4: 当父类没有无参构造方法的时候, 你必须要做 子类初始化之前先初始化 父类这一点,
访问 父类的其他有参构造。
访问 子类其他的构造方法。 无论哪一种, 你只需要做到 子类初始化之前 必须先初始化父类就可以。
5: 子类初始化之前,必须先初始化父类,初始化几次 一次(必须一次),所以 this() 和super() 都必须放在第一行,所以他俩不能共存。
以上细节:你只需要记住原理,就不会忘。 子类初始化之前必须先初始化父类,而且必须是初始化一次。
内存图
方法区 是用来存储 类的 字节码文件的, 就是代码的存储位置。
堆内存 用来存储 对象的数据的。
栈内存 代码一句一句的执行 在栈里面发生的, 栈里面是以方法为单位。
类的代码存储在 方法区中的。
对象 是创建在 堆内存中的, 我们给对象中的属性赋值, 是赋值到堆内存中的。
- 类的字节码文件, 只有第一次使用到的时候, 加载一次, 以后再执行到的时候 就不再加载了。
- 类的字节码文件在 方法区的存储,是跟着jvm的, 只要jvm不停止, 方法区里面的东西就不会清楚。
- 引用类型的数据 每new一次 就是要在堆内存开辟新的空间。
- 可以多个变量同时去操作 一个对象(堆内存中的), 只要其中一个通过变量去变量 对象里面的内容,第二变量再去使用对象里面的内容的时候 就已经发生改变了。
- 当一个应用类型变量 不指向堆内存中任何内容了, 这时候再通过这个变量去访问内容 就会报空指针异常
- 引用类型作为参数,传递的是地址值。
- 类当中存在的成员变量在堆内存中也有一份, 堆内存都有默认初始化值。
- 成员方法不会进入堆内存, 但是会有一块专门的空间存放成员方法的地址 ,通过地址找到方法区字节码文件的成员方法
垃圾回收
- 栈内存:方法被调用 就会在栈内存存在,
方法执行结束之后,从栈内存中消失了,包括里面的 变量 以及基本类型的数据值 都消失了。
- 堆内存:当指向这个堆内存中的对象的 所有的变量 都消失了的时候。
这时候 这个对象 就会变成内存垃圾,但是并不会立刻消失。 而是 java底层有一个垃圾回收机制 定时轮训 每隔一段时间就去检测垃圾,并且回收垃圾。
- 方法区:方法区中的内容, 只要jvm不关闭,任何时候都不消失。
关键字:private
-
private只能修饰 成员 不能修饰局部, 被private修饰的成员 不能在类的外部直接访问的。 只能在本类内进行访问.
-
在java中,为了保证数据的安全性,我们会把数据定义为private等(私有、封装),如果想要调用就会用到set()方法与get方法或者构造函数方法。这里说的是第一种方法,set()与get(),既然说到这个,就得说一下封装的概念。
-
Setter:赋值 Getter:取值,在哪儿用:在构造方法中初始化属性值的用或者对象名.set/get()进行使用!!!
-
- 好处:隐藏内部实现细节,保证代码的安全,同时也可以提升数据的安全性
- 访问修饰符,从访问的范围由小及大依次是:private default(当什么都不写默认就是这个) protected public
- 访问修饰符的作用:可以修饰类、接口、属性和方法。通常可以通过访问修饰符来改变所修饰对象的可访问范围。
关键字:this
作用:可以调用本类的成员(变量、方法)解决局部变量和成员变量的重名问题。
-
变量都遵循就近原则,在执行过程中形参和成员变量重名时会优先使用局部变量,局部变量没有时才会去成员变量中找。
-
所以就出现了this关键字
-
- 方法的第一个参数全部隐藏了,第一个参数就是自身对象的地址值。用this关键字来代替。
-
this打破就近原则:
-
- 局部变量是无法通过对象直接调用的:
public class Student {
int age;
public void show(){
int aa=10;
System.out.println(aa);
}
}
public class Demo {
public static void main(String[] args) {
// 此处如何拿到age
Student stu = new Student();
System.out.println(stu.age);
//如何拿到aa
//System.out.println(stu.aa); //编译报错 。局部变量拿不了。 因为 局部变量 在栈里面 随着方法调用才存在, 方法没调用 aa是不存在的
// 所以局部变量 只有在show方法内部去使用。
stu.show();
}
}
- this代表的是对象(指的是当前自己对象的地址)。 对象.变量 此处调用的变量是 成员变量。 所以this.a 此处的a就是成员变量。
class Student {int a = 10; public void show(){ int a = 20; System.out.println(this.a); //10 }}
- this 指的是一个"动态"的对象 , 谁来调用包含this的这个方法,this就指的是谁。只有是这个类的对象可以调用, 所以 “谁”只能是本类对象。
构造方法
- 定义:用来创建对象 所必需要执行的方法。
- 定义格式:
-
- 方法名字 必须和类名一摸一样
- 构造方法 没有返回值类型的, void也不能有
- 构造方法必须 只能用 new来调用。用来创建对象。不能使用对象去调用。
- 不能写返回值 (不能由return带回具体的结果,但是可以单独写一个 return , 目的是为了强制终止构造方法)
class Student {
public Student(){
// 这就是Student类的一个构造方法。
}
}
构造方法创建对象,每创建一次就会执行一次构造方法。
构造方法的注意事项
-
1:构造方法 顾名思义 “构造” 也就是说 这个方法是为了创建对象而存在的,换句话说, 如果一个类中没有构造方法 那么这个类创建不了对象。
-
- 如果一个类中 没有手动写出构造方法,那么系统 会提供给你一个 构造方法。自己写了之后系统就不再提供。
-
2: 构造方法可以重载
-
3: 构造方法有参作用, 在创建对象的时候 顺便给 属性赋值
封装
封装特性:
- private 提高安全性
- 方法 提高了代码的复用性。
- 封装数据 JavaBean类–作用用来封装数据的。
- 封装:隐藏实现细节,仅对外暴露公共的访问方式。
- private私有化成员属性,提供公开public的getter/setter
将属性抽取到类当中,这是对数据的一种封装。
将代码抽取到方法中,这是对代码的一种封装。
-
类: 按照功能来划分
-
- 工具类: 这种类 里面 包含了大量的方法 每个方法里面都有明确的功能,这种类里面通常没有属性。
class Uitls {
public void show(){
}
public void method(){
}
....
}
- 测试类 :能够运行的类,含有main方法。
class Demo {
public static void main(String[] args){
}
}
class Demo1 {
@Test
public void show(){
}
}
- JavaBean类 :这种类里面 几乎全部都是属性, 每个属性都有private 并且 setget方法。这种类 是用来封装数据用的。
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Demo {
public static void main(String[] args){
//数组
//int[] arr = new int[]{10,20,30};
int[] arr = new int[2];
arr[0] = 10;
arr[1] = 20;
//Student stu = new Student(13,"zhangsan");
Student stu = new Student();
stu.setAge(13);
stu.setName("张三");
}
}
1. 继承
1.1 继承的实现
- 继承的概念:继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法
- 实现继承的格式:实现继承的格式通过extends实现
- 格式:class 子类 extends 父类 { }
注意事项:子类继承父类 不能访问父类 私有的内容, (是因为 自己权限不足)
1.2 Java中继承的特点
- Java中类只支持单继承,不支持多继承
错误范例:class A extends B, C { }
- Java中类支持多层继承
多层继承示例代码:
public class Granddad {
public void drink() {
System.out.println("爷爷爱喝酒");
}
}
public class Father extends Granddad {
public void smoke() {
System.out.println("爸爸爱抽烟");
}
}
public class Mother {
public void dance() {
System.out.println("妈妈爱跳舞");
}
}
public class Son extends Father {
// 此时,Son类中就同时拥有drink方法以及smoke方法
}
2. 继承中的成员访问特点
2.1 继承中变量的访问特点
在子类方法中访问一个变量,采用的是就近原则。
1. 子类局部范围找
2. 子类成员范围找
3. 父类成员范围找
4. 如果都没有就报错(不考虑父亲的父亲…)
示例代码
class Fu {
int num = 10;
}
class Zi {
int num = 20;
public void show(){
int num = 30;
System.out.println(num);
}
}
public class Demo1 {
public static void main(String[] args) {
Zi z = new Zi();
z.show(); // 输出show方法中的局部变量30
}
}
2.2 继承中构造方法的访问特点(理解)
注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过super去手动调用父类的带参的构造方法
注意: this(…)super(…) 必须放在构造方法的第一行有效语句,并且二者不能共存
2.3 继承中成员方法的访问特点(掌握)
通过子类对象访问一个方法
- 子类成员范围找
- 父类成员范围找
- 如果都没有就报错(不考虑父亲的父亲…)
系统默认在前面加一个this.
1.多态
1.1多态的概述
- 什么是多态
同一个对象,在不同时刻表现出来的不同形态
- 多态的前提
-
- 要有继承或实现关系
- 要有方法的重写(非必须)
- 要有父类引用指向子类对象
有方法的重写(非必须的),之所以很多的教程中说必须有方法的重写,是因为 我们去应用多态的时候99%以上的情况 全部都是用方法的重写, 如果
你没有方法的重写,那么你使用多态就没有任何意义,所以 很多的教程中 必须有方法的重写。
- 代码演示
class Animal {
public void eat(){
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test1Polymorphic {
/*
多态的前提:
1. 要有(继承 \ 实现)关系
2. 要有方法重写
3. 要有父类引用, 指向子类对象
*/
public static void main(String[] args) {
// 当前事物, 是一只猫
Cat c = new Cat();
// 当前事物, 是一只动物
Animal a = new Cat();
a.eat();
}
}
1.2多态中的成员访问特点
总体结论: 多态中除了重写的方法是调用的子类的 其他所有所有的东西都是调用父类的 如果父类没有就报错。
- 代码演示
成员变量:
class Person {
int age = 80;
int a = 20;
}
class Student extends Person {
int age = 25;
int b = 30;
}
class Demo {
public static void main(String[] args){
Person p = new Student();
System.out.println(p.age); // 80
System.out.println(p.a); // 20
//System.out.println(p.b); // 编译报错
}
}
// 结论:多态中调用成员变量 全部都是调用父类的 父类没有就编译报错。
成员方法:
class Person {
public void show(){
System.out.println("Person");
}
public void method(){
System.out.println("method");
}
}
class Student extends Person {
public void show(){
System.out.println("Student");
}
public void function(){
System.out.println("function");
}
}
class Demo {
public static void main(String[] args){
Person p = new Student();
p.show(); // Student
p.method(); // method
//p.function(); //编译报错, 因为父类没有
}
}
//结论: 多态中调用成员方法 除了重写的方法 是调用子类的 其他的方法都是调用父类的 父类没有就报错。
1.3多态的好处和弊端
-
好处:提高程序的扩展性。
-
- 具体体现:定义方法的时候,使用父类类型作为参数,该方法就可以接收这父类的任意子类对象。
-
弊端:父类的引用不能调用子类特有的内容的。
1.4多态中的转型
- 向上转型:父类引用指向子类对象就是向上转型
- 向下转型:格式:子类型 对象名 = (子类型)父类引用;
- 代码演示
class Fu {
public void show(){
System.out.println("Fu..show...");
}
}
class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi..show...");
}
public void method(){
System.out.println("我是子类特有的方法, method");
}
}
public class Test3Polymorpic {
public static void main(String[] args) {
// 1. 向上转型 : 父类引用指向子类对象
Fu f = new Zi();
f.show();
// 多态的弊端: 不能调用子类特有的成员
// f.method();
// A: 直接创建子类对象
// B: 向下转型
// 2. 向下转型 : 从父类类型, 转换回子类类型
Zi z = (Zi) f;
z.method();
}
}
3.5多态中转型存在的风险和解决方案 (应用)
- 风险
如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException(类型转换异常)
- 解决方案
- 关键字:instanceof
- 使用格式
变量名 instanceof 类型
通俗的理解:判断关键字左边的变量,是否是右边的类型,返回boolean类型结果
- 代码演示
abstract class Animal {
public abstract void eat();
}
class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}
public void watchHome(){
System.out.println("看家");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
}
public class Test4Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}
public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome();
// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 类型转换异常
// 判断a变量记录的类型, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}
}
类型转换:
基本类型:
自动类型转换
int a = 10;
long lo = a;
强制类型转换
long lo = 100L;
int a = (int)lo;
引用类型(类):
自动类型转换
class Animal {
}
class Cat extends Animal{
}
Cat c = new Cat(); //猫 (范围小)
Animal a = c; // 自动类型转换, 多态 , 向上转换型。
// Animal a = new Cat(); // 一样的
强制类型转换
Animal a = new Cat(); // 猫是一只动物, 但是你虽然看到的是动物 他本质是一只猫
Cat c = (Cat)a; // 强制类型转换, 向下转型。
Animal a1 = new Dog(); // 狗是一只动物 但是本质却是条狗
//Cat c1 = (Cat)a1; // 运行报错 类型转换错误
// instanceof 前面要放一个 对象, 后面要放一个 类的名字
// a instanceof A
// a 是A的一个实例吗 如果是 那么这个表达式就是true 如果不是 那么这个表达式就是false
boolean b = a1 instanceof Cat;
if (b){
Cat c1 = (Cat)a1;
}