目录
一、封装
1.访问修饰符
- 外部类只能有两种访问级别控制 : public 和默认
- 外部类是不能用 private 和 protected 修饰的
- 使用 public 的外部类可以被所有的类使用 , 不使用任何访问控制符的外部类只能被同一个包中的其他类使用
- Java文件中如果所有类都没有用 public 修饰 , 这个 java 文件的文件名可以是任何一切文件名 , 但如果 Java 文件里有了一个 public 修饰的类名 , Java文件名必须和含有 public 修饰的类的类名相同
package lee1;
public class Person {
private String name;
private int age;
public void setName(String name)
{
if(name.length()>6||name.length()<2)
{
System.out.println("您设置的人名不符合要求");
return ;
}
else
{
this.name=name;
}
}
public String getName()
{
return this.name;
}
public void setAge(int age)
{
if(age>100||age<0)
{
System.out.println("您输入的年龄不合法!");
return ;
}
else
{
this.age=age;
}
}
public int getAge()
{
return this.age;
}
}
package lee1;
public class PersonTest {
public static void main(String [] args)
{
Person p = new Person();
//由于 age 成员已经隐藏,所以下面语句将出现错误
//p.age = 10000;
p.setAge(1000);
//以下程序会输出0 ,原因是:age超出了我Person设定的方法里的age里的年龄范围
//所以 输入的1000 并没有实际的赋予到 set方法里面, age 还是为0
System.out.println("未能设置 age 成员变量时: " + p.getAge());
p.setAge(40);
System.out.println("成功设置 age 成员变量时: " + p.getAge());
p.setName("小明");
System.out.println("成功设置 name 成员变量时: " + p.getName());
}
}
访问符的使用规则:
- 类里面的绝大部分成员变量都应该使用 private 使用
- 只有一些 static 修饰的 , 类似全局变量的成员变量 , 才用 public 修饰
- 有些只用于辅助实现该类的方法 ( 工具方法 ) ,应该用 private 修饰
- 如果某个类要做其他类的父类 , 并且该类的大部分方法可能仅希望被子类重写 , 而不被外不调用 , 使用 protected
- 大部分外部类都用 public 修饰
二.包
1.包的定义和目的
包允许将类组合成较小的单元(类似文件夹),它基本上隐藏了类,并避免了名称上的冲突。包允许在更广泛的范围内保护类、数据和方法。
包的目的:
- 区分相同名称的类。
- 能够较好地管理大量的类。
- 控制访问范围。
package 语句应该放在源文件的第一行
在每个源文件中只能有一个包定义语句,
并且 package 语句适用于所有类型(类、接口、枚举和注释)的文件
注意:如果在源文件中没有定义包,那么类、接口、枚举和注释类型文件将会被放进一个无名的包中,也称为默认包
//包的定义格式:
//package 包名;
package lee;
public class Hello {
public static void main(String [] args)
{
System.out.println("Hello,World!");
}
}
//像这个程序,就是被放在 lee 包下面
/*
为Java类添加包必须在 Java 源文件中通过 package 语句指定!!!!!
单靠目录名是没有用的!!!!!!!
*/
Java包机制:
- 源文件里使用 package 语句指定包
- class 文件必须放在对应的路径下
2.包的访问
- 访问包可以使用 import , 访问格式是 : import 包名+类名 ;
- import 语句位于 package 和类之间
- 访问相同包下的另一个类 , 无需使用包的前缀
- 父包跟子包没有任何关系 , 所以父包的类需要使用子类的包的时候 , 不能省略父包部分
- 调用构造器的使用需要在构造器执之前添加包后缀
- 将某个包下的所有类导入的格式为: import 包.*;
3.Java常用包
Java有很多包 , 这些不用特意去记忆 , 程序写多了自然就会了
二、构造器
1.构造器的目的
构造器是一种特殊的方法 , 这个特殊的方法用于创建实例时执行初始化 .
构造器是创建对象的重要途径 , Java类必须包含一个或者其以上的构造器
构造器最大的用处就是在创建对象时执行初始化!!!!
如果没有为类编写一个构造器 , 系统会为该类提供一个默认的构造器
(这也是成员变量为什么一定有初始值的原因 , 系统提供的默认构造器里面会自动初始化)
如果为一个类提供了构造器 , 系统不会再提供构造器
//构造器的名字必须与类名相同!!!!!!!
package lee1;
import lee.*;
public class ConstructorTest {
public String name;
public int age;
private static String teacher_name = "小花老师";
public ConstructorTest(String name,int age)
{
this.name=name;
this.age=age;
}
public static void main(String [] args)
{
ConstructorTest a = new ConstructorTest("小明",18);
System.out.println(a.name);
System.out.println(a.age);
}
}
构造器主要用于被其他方法调用 用以返回该类的实例
所以通常将构造器设置为 public 权限
2.构造器重载
构造器重载:
1.同一个类
2.与类名相同
3.形参列表不同
package lee;
public class ConstructorOverload {
public String name;
public int count;
public int age;
public ConstructorOverload()
{
}
public ConstructorOverload(String name ,int count){
this.name=name;
this.count=count;
}
public static void main(String [] args)
{
ConstructorOverload a = new ConstructorOverload();
ConstructorOverload b = new ConstructorOverload("小明", 18);
System.out.println(a.name + " " + a.count);
System.out.println(b.name + " " + b.count );
}
}
在一个构造器中调用另一个构造器:使用 this 关键字
使用 this 关键字调用另一个重载构造器只能在构造器中使用
并且必须作为构造器执行体的第一条语句
软件开发的一个原则:
不要把相同的代码重复书写两次以上
尽量避免相同的代码重复出现
充分复用每一段代码
让程序代码更简洁,也可以降低软件的维护成本
三、继承
1.继承的定义及特点
- 继承是面向对象三大特征之一
- Java的继承是单继承 , 每一个子类只有一个父类
- 父类的范围总要比子类大
子类继承父类的格式:
修饰符 class Subclass extends SuperClass
{
//类定义部分
}
extends:扩展
子类是父类的拓展,子类可以获得父类的成员变量,方法,内部类(包括内部借口,枚举)
Java的子类不能获得父类的构造器,初始化块 !!!
package lee;
public class Fruit {
public double weight;
public void info()
{
System.out.println("我是一个水果!重量为:" + weight + "g");
}
}
package lee;
public class Apple extends Fruit
{
public static void main(String [] args)
{
Apple a = new Apple();
a.weight = 65;
a.info();
}
}
Java 只能有一个直接父类 Java可以拥有无限个间接父类: 比如:地球包含动物,动物包含人,人包括学生,老师等等 又比如:地球包括植物,植物包括水果,水果包括苹果 区别为:水果是苹果的直接父类,植物是水果的间接父类
如果定义一个Java类的时候未显式指定这个类的直接父类 , 则这个类默认扩展为 java.lang.Object 类
2.方法重写
1.方法重写的定义:
子类扩展了父类 , 子类是一个特殊的父类.
大部分情况下 , 子类总是以父类为基础 , 额外增加新的成员变量和方法 , 但是有的时候父类的某些方法不适用于子类 , 这时候就需要方法重写
重写父类的方法:
子类包含父类同名方法的现象叫做方法重写 , 也称为方法覆盖
子类重写父类的方法或者子类覆盖了父类的方法
public class Bird {
public void fly()
{
System.out.println("我在天空自由翱翔....");
}
public void say(String name ,int age)
{
System.out.println("我的名字是:" + name + "\n我的年龄是: " + age);
}
}
package lee;
public class Ostrich extends Bird
{
public void fly()
{
System.out.println("我只能在地上跑!");
}
public void say(int age,String name)
{
System.out.println("我的名字是:" + name + "\n我的年龄是: " + age);
}
public static void main(String[ ] args)
{
Ostrich a = new Ostrich();
a.fly();
a.say(18,"鸵鸟");
}
}
2.方法重写的规则和注意事项
方法重写的规则:
两同两小一大:
两同:
1.方法名相同
2.形参列表相同
两小:
1.子类方法返回值应该比父类方法返回值类型更小或相等
2.子类方法声明抛出的异常类应该比父类方法声明抛出的异常类更小或相等
自己记忆:儿子继承父亲的财产
儿子拿到的财产要么和父亲拥有财产数额相同
要么比父亲拥有的财产数额小
异常就是出错,发生错误:
就好比人的一生
父亲已经在之前的人生里碰了壁,吃了苦,积累了经验
所以儿子经历人生的时候,如果父亲传授经验,那么儿子就会少走很多弯路
如果父亲没有传授经验, 那么儿子就会和父亲吃同样多的苦
一大:
子类的访问权限要比父类的访问权限更大或者相等
- 覆盖方法和被覆盖方法要么是类方法 , 要么是实例方法 , 不能一个是类方法 , 一个实例方法
- 若父类方法用 private 修饰,那么子类无法访问该方法
- 子类中如果定义了一个和父类private方法具有相同的方法名 , 相同的形参列表,相同返回值类型的方法 , 这不叫方法重写 , 而是在子类重新定义了一个新方法
3.super关键字
super关键字的用法:
- 指向父类对象;
- 调用父类的方法;
- super() 可以调用父类的构造方法。
super用于限定对象调用从父类继承得到的实例变量或方法 , 它指向的是对象 , 所以他也不能出现在 static 修饰的方法中
package lee;
public class SubClass extends BaseClass
{
public int a = 7;
public void accessOwner()
{
System.out.println(a);
}
public void accessBase()
{
//通过super来限定访问从父类继承得到的 a 实例变量
System.out.println(super.a);
}
public static void main(String[ ] args)
{
SubClass kk = new SubClass();
kk.accessBase();
kk.accessOwner();
}
}
class BaseClass
{
public int a = 5;
}
当子类继承了父类的实例变量的时候,例如上面的例子:
SubClass实际分配了两块内存,一块存放子类定义的实例变量
一块存放从BassClass类继承得来的实例变量
而super 关键字的作用就是限定访问从父类那里继承得来的 a 实例变量
如果子类里面未含有和父类同名的成员变量,那么子类访问成员变量的时候
就不需要用super或者父类名来调用
如果某个方法含有 a 的成员变量,但没有显式调用者,系统查找顺序:
1.方法中是否含有名为 a 的局部变量
2.当前类中是否含有名为 a 成员变量
3.查找 a 的直接父类中是否含有 a 的成员变量,直到java.lang.Object类,
如果最终都无法查找到,那么系统出现编译错误
子类中定义于父类同名的实例变量并不会完全覆盖父类中定义的实例变量 , 他只是隐藏了父类的实例变量
package lee;
public class HideTest
{
public static void main(String [] args)
{
Derived a = new Derived();
//System.out.println(a.tag);
//这里报错
//原因就是在子类Derived里面定义的tag是私有变量,不允许访问
System.out.println(((Parent)a).tag);
//将a 显式向上转型为Parent,即可访问tag 实例变量
//所以输出的时候会输出的是 Parent 里的 tag 内容
}
}
//由此可见,当子类的成员变量与父类同名时
//父类的成员变量只是被隐藏起来,而并不是被子类代替
//子类和父类重名的成员变量是分配在了不同的内存里面
class Parent
{
public String tag = "我不知道写啥....";
}
class Derived extends Parent
{
private String tag = "我还是不知道写啥....";
}
4.调用父类构造器
通过super 关键字实现
package lee;
class Base
{
public double size;
public String name;
public Base (double size,String name)
{
this.size = size;
this.name = name;
}
}
public class Sub extends Base
{
public String color;
public Sub(double size,String name ,String color)
{
//通过super调用来调用父类构造器的初始化过程
super(size,name);
this.color=color;
}
public static void main(String [] args)
{
Sub a = new Sub(9.0,"测试对象","红色");
System.out.println(a.size + "--" + a.name + "--" + a.color );
}
}
super 和 this 调用也很像
区别:
- super 调用的是其父类的构造器
- this 调用的是同一个类中的重载的构造器
- 同时 super 调用父类构造器必须出现在子类构造器执行体的第一行
- 所以 this 调用和 super 调用不会同时出现
不管是否使用 super 关键字调用执行父类的代码,子类构造器总会调用父类构造器一次
子类调用父类构造器运行机制 :
- 子类构造器执行体的第一行使用super 显式调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器。
- 子类构造器执行体的第一行代码使用 this 显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时也会先调用父类构造器。
- 子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。
四、多态性
1.多态的理解
多态性的理解:不同对象对同一消息, 方法的不同响应
就像午饭时间到了,同学们要吃饭
有的同学选择吃饺子,有的同学选择吃粉丝,
有的同学选择吃汉堡包,炸鸡,有的同学选择吃水果....
这就是多态,相同类型的变量,调用同一个方法呈现出多种不同的行为特征,这就是多态
package lee;
class BaseClass1//如果增加一个public 则需写入一个文件中,建立一个新类
{
public int book = 5;
public void base()
{
System.out.println("父类的普通方法");
}
public void test()
{
System.out.println("父类被覆盖的方法");
}
}
public class SubClass1 extends BaseClass1
{
//这里的book实例变量隐藏了父类的book实例变量
public String book = "不知道写啥,写个废话文学";
public void test()//子类SubClass1在这里对父类的test进行了覆盖,也就是方法重写
{
System.out.println("子类覆盖父类的方法!");
}
public void sub()
{
System.out.println("子类的普通方法!");
}
public static void main(String [] args)
{
//定义的 a 对象:编译时类型和运行时类型完全一样
//调用父类BaseClass1成员变量和方法
BaseClass1 a = new BaseClass1();
System.out.println(a.book);
a.base();
a.test();
//定义的 b 对象:编译时类型和运行时类型完全一样
//调用子类SubClass1的成员变量和方法
SubClass1 b = new SubClass1();
System.out.println(b.book);
b.base();
b.test();
b.sub();
//定义的 c 对象:编译时类型和运行时类型不一样,这里发生多态
//
BaseClass1 c = new SubClass1();
System.out.println(c.book);
c.base();
c.test();
//因为BaseClass1没有提供sub方法,所以会报错
// c.sub();
}
}
编译时类型: 声明该变量使用的类型决定
运行时类型: 由实际赋给该变量的对象决定
如果编译时类型与运行时类型不一致,就可能出现所谓的多态
子类是一种特殊的父类
Java允许把一个子类对象直接赋给一个父类引用变量
无需任何类型转换 , 或者称为向上转型
向上转型系统自动完成
当一个子类对象赋给父类引用变量并且调用该引用变量的方法时候,
其方法行为总是表现为子类方法的行为特征,而不是父类的方法特征
与方法不同的是,对象的实例变量不具备多态性
注意:
引用变量在编译阶段只能调用其编译时类型所拥有的方法,但运行时则执行运行时类型所具有的方法
举个简单的例子:
Object p = new Person()
这个p只能调用Object里面的方法,不能调用Person里面的方法
2.强制类型转换
1.强制类型转换规则:
引用类型的强制转换的目的: 为了让引用变量调用它运行时类型的方法.
package lee1;
public class ConversionTest {
public static void main(String [] args)
{
double d =3.14;
long l = (long) d;
System.out.println(d);
System.out.println(l);
int in = 5;
//下面这个转换会报错:
//数值类型不能和布尔类型转换
//boolean in1 = (boolean) in;
Object obj = "Hello";
//obj变量的编译时类型为Object,Object与String存在继承关系
//所以可以强制类型转换
//objStr的实际类型也是String 所以可以通过
String objStr = (String) obj;
System.out.println(objStr);
Object objPri = Integer.valueOf(5);
//objStr变量的编译时类型为Object,而其运行时类型为Integer
//Object 与 Integer 存在继承关系, 所以可以强制类型转换
//objStr的实际类型也是 Integer ,会引发ClassCastException异常
//String str = (String) objPri;
//所以为了防止发生ClassCastException异常
//引用instanceof运算符来判断是否可以转换成功
if(objPri instanceof String )
{
String str = (String) objPri;
}
}
}
注意:
- 把子类对象赋给父类引用变量 , 被称为向上转型
- 侧面证实了子类是一种特殊的父类.
3.instanceof运算符
进行强制类型转换的时候 , 有可能出现异常 , 为了避免出现ClassCastExpection异常 , 引进instanceof 运算符先进行判断是否可以进行强制类型转换 , 从而来保证程序更加健壮
- instanceof 运算符前面通常是一个引用类型变量 , 后面通常是一个类 (也可以是接口,可以把接口理解成一种特殊的类)
- 它用于判断前面的对象是否是后面的类或者是子类或者是实现类的实例
- 是返回true 不是返回false
五、继承与组合
1.继承使用注意点
1.继承注意事项和缺点
- 子类扩展父类 , 会继承父类的成员变量和方法 (跟访问关系没关系 , 一定会继承)
- 只有在访问权限的允许下 , 子类才能直接访问 (复用) 父类的成员变量和方法
继承带来的缺点:
- 继承严重得破坏了父类的封装性 , 父类的实现细节对子类不再透明 , 子类可以访问甚至改变父类的是实现细节 (例如 , 通过方法重写来改变父类的实现)
2.创建父类应遵循的规则:
- 尽量隐藏父类的内部数据 , 尽量父类的所有成员变量都设置为 private
- 不要让子类可以随意访问 , 修改父类的方法
- 尽量不要在父类构造器中调用将要被子类重写的方法
3.什么时候才使用继承?
- 子类需要额外增加成员变量 . 例如人->学生 ,但是学生又分了年纪 ,这个时候就需要继承
- 子类需要增加自己独有的行为方式 . 例如 人->各种职业 , 各种职业有对应的行为 , 比如学生学习 , 老师教学.....
2.组合实现复用
组合是指在新的类中创建原有类的对象,重复利用已有类的功能。
package lee1;
public class CompositeTest {
public static void main(String [] args)
{
//此时需要显示创建被组合的对象
Animal a = new Animal();
Bird b = new Bird(a);
b.breathe();
b.fly();
Animal a1 = new Animal();
Wolf c = new Wolf(a1);
c.breathe();
c.run();
}
}
class Animal
{
private void beat()
{
System.out.println("心脏跳动....");
}
public void breathe()
{
beat();
System.out.println("吸一口气,吐一口气,呼吸中....");
}
}
class Bird
{
//将原来的父类组合到原来的子类,作为子类的一个组合成分
private Animal a;
public Bird (Animal a)
{
this.a=a;
}
public void breathe()
{
//直接复用Animal提供的breathe()来实现Bird的breathe()方法
a.breathe();
}
public void fly()
{
System.out.println("我在天空自由飞翔....");
}
}
class Wolf
{
private Animal a ;
public Wolf (Animal a)
{
this.a = a;
}
public void breathe()
{
a.breathe();
}
public void run()
{
System.out.println("我在陆地上快速奔跑....");
}
}
注意:
- 使用组合复用虽然需要创建新的对象 , 但是这并不意味着组合关系时系统开销更大
- 就拿上面的举例子:
- 继承 : 假设父类定义了2个实例变量 , 子类定义了3个实例变量 , 在创建子类实例时 , 会分配5个内存空间 , 父类的信息只是被隐藏了 , 而不是完全没有!!!! 吃水不忘打井人!!!
- 而组合复用 : 创建被嵌入实例需要2个内存空间 , 创建整体空间需要3个内存空间 , 总共需要5个内存空间 , 这样看 , 继承和组合的系统开销不会有本质的差别
3.继承和组合对比
组合自己理解:
举个实际化的例子
小明和小明爸爸A, 小明妈妈是有明确的血缘关系 , 这个可以理解为一种继承
现在小明爸爸A和小明妈妈离婚了 , 小明妈妈带着小明重组了一个家庭
而现在的小明爸爸B虽然是小明的父亲
但是小明和小明爸爸B是没有血缘关系的, 但是他们成为了一个家庭
这个就叫组合
再举个例子:
李刚和张三本来两人之间没关系
可是因为同在3班这个事件
让他们产生了关系,这个就成为组合
组合就是:本来没有关系 , 但是通过某些行为而产生了联系称为组合
继承则是:一开始就有关系
继承表达的是一种 "是(is-a)"的关系 , 而组合表达则是 "有(has-a)"的关系
总结
Java看了挺多 , 但是写总结的时候还是发现自己有许多纰漏 , 需要重新翻书
然后就是蓝桥杯要努力刷题