根据尚硅谷老师视频笔记总结
面向对象(上)
java面向对象学习的三条主线
- Java类及类成的成员:属性、方法、构造器、内部类
- 面向对象的三大特征:封装、继承、多态、(抽象性)
- 其他关键字:this、super、static、final、abstract、interface、package、import
“大处着眼,小处着手”
面向过程与面向对象
二者都是一种思想,面向对象是相对于面向过程而言的.面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做.面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做.
-
经典例子:人把大象装冰箱
-
面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做.
- 把冰箱门打开
- 抬进大象,塞进冰箱
- 把冰箱门关上
-
面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做.
-
人{打开(冰箱){ 冰箱.开开(); } 抬起(大象){ 大象.进入(冰箱); } 关闭(冰箱){ 冰箱.闭合(): } }
-
冰箱{ 开开(){} 闭合(){} }
-
大象{ 大象进入(冰箱); }
-
-
类和对象
- 类;类是对一类事物的描述,是抽象的,概念上的 定义
- 对象:对象是实际存在的该类事物的每个个体,因而也称为实例(instance)
- 万物结为 对象
面向对象程序设计的重点就是类的设计
设计类、就是设计类的成员
package object1;
/*
*设计类,其实就是设计类的成员
* 属性=成员变量=field=域、字段
* 方法=成员方法=函数=method
*
* 创建类的对象=类的实例化=实例化类
*
* 类和对象的使用(面向对象思想落地的实现)*/
//测试类
public class OOPTest {
public static void main(String[] args) {
//2.创建Person类的对象
Person p1=new Person();
//3.调用对象的结构,属性、方法
//调用属性 对象.属性
p1.name="Tom";
p1.isMale=true;
System.out.println(p1.name);
//调用方法 :"对象.方法"
p1.eat();
p1.sleep();
p1.talk("chinese");
}
}
//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);
}
}
运行结果:
Tom
人可以吃饭
人可以睡觉
人可以说话,使用的是chinese
Process finished with exit code 0
- 如果创建了一个类的多个对象呢,则每个对象都独立的拥有一套类的属性.(非static的),意味着:我们修改一个对象的属性a,则不影响另外一个对象属性a的值
- 如何是将一个对象地址赋值给另一个,那么他们将都会指向堆空间中的同一个对象实体
对象的内存解析
* 方法的代码是存在方法区的.
- 注意,其实"Tom"是在常量池了,这里先这样写
成员变量(属性)VS 局部变量
类中属性的使用
* 属性(成员变量)VS局部变量
* 1.相同点
* 1.1定义变量的格式:数据类型 变量名=变量值
* 1.2先声明,后使用
* 1.3变量都有其对应的作用域
* 2.不同点
* 2.1在类中声明的位置不同
* 属性:直接定义在类的一对{}内
* 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
* 2.2关于权限修饰符的不同
* 属性:可以在声明属性时,指明其权限,使用权限修饰符,常用的权限修饰符:private、public、缺省、protected -->靠后说
*目前,大家声明时候,都使用缺省的
* 局部变量:不可以使用权限修饰符 (可以使用final)
* 2.3默认初始化值的情况
* 属性:类的属性,根据其类型,都有默认初始化值
* 整形(byte、short、int、long) 0
* 浮点型(float、double) 0.0
* 字符型(char) 0 (或'\u0000')
* 布尔型(boolean) false
* 引用数据类型(类、数组、接口) null
* 局部变量:没有默认初始化值
* 意味着, 我们在调用局部变量之前,一定要显示赋值
* 特别的:形参在调用时, 我们赋值即可
* 2.4在内存中加载的位置
* 属性:加载到堆空间中(非static), static的放在方法区
* 局部变量:加载到栈空间
public class UserTest {
public static void main(String[] args) {
User user = new User();
user.eat("饭");
}
}
class User{
String name;
int age;
boolean isMale;
public void tale(String language){//language形参
System.out.println("我们使用"+language+"进行交流");
}
public void eat(String f){
//局部变量
String food=f; //这样也相当于给局部变量赋值了,知识f没赋值而已
System.out.println("北方人喜欢吃"+food);
}
}
结果:
北方人喜欢吃饭
方法
方法:描述类应该具有的功能
比如:
- Math类:sqrt()\random()\…
2. Scanner类:nextXxx()…
3. Arrays类:sort()\binarySearch()\toString()\equals()\…
-
方法的声明:权限修饰符 返回值类型 方法名(形参列表){
方法体
}
3.说明:
3.1关于权限修饰符、默认方法的权限修饰符都是public
java规定的四种权限修饰符:private、public、缺省、protected -->封装性细说
3.2返回值类型:有返回值vs没有返回值
3.2.1如果方法有返回值,则必须在方法声明时,指定返回值类型,通过return关键字返回指定类型的变量或常量
如果方法没有返回值,用void表示就行,通常没有返回值的方法中就不需要return,但是使用只能写return; 表示结束此方法;
3.2.2我们定义方法该不该有返回值?
①题目要求
②凭经验:具体问题具体分写
3.3 方法名:属于标识符 做到见名知意
3.4 形参列表,方法可以声明0,1,2,...
格式:数据类型1 形参1 数据类型2 形参2
3.5 方法体:方法功能的实现
4.方法的使用中,可以使用当前类的属性或方法
方法中不可以定义方法
return关键字
- 适用范围:使用在方法体中
- 作用:①结束方法 ②针对于有返回值类型的方法,使用"return 数据"方法返回所要的数据
- 注意点:return 关键字后面不可以声明执行语句
万事万物皆对象
-
在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
Scanner,String等
文件,File
网络资源:URL
-
涉及到Java语言与前端Html、后端的数据库交互时,前后端的结构在Java层面交互式,都体现为类、类
匿名对象的使用
- 两个匿名对象是不一样的
new Phone().方法名1():
new Phone().方法名2():
- 理解:我们创建的对象,没有显示的赋给一个变量名,即为匿名对象
- 特征:匿名对象只能调用一次,再调就是另一个对象了
public class StudentTest {
public static void main(String[] args) {
new Phone().price=1;
new Phone().price();
}
}
class Phone{
int price=1000;
public void price(){
System.out.println(price);
}
}
结果:
1000
- 使用如下,可以传给形参让形参引用
再谈方法
方法的重载
在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可.
两同一不同:同一个类,相同方法名
参数列表不同:参数个数不同,参数类型不同 (不同类型参数顺序不一样也算是重载)
跟权限修饰符无关
我认为子类重载父类方法也是重载,因为子类继承了父类的方法,有父类的方法,再写同名不同参数的方法时也叫重载,和重载必须在同一个类中应该不矛盾
举例子:
Arrays类中重载的sort()/binarySearch()
//调用方法中,如何确定一个指定的方法:
方法名--->参数列表
public class StudentTest {
public static void main(String[] args) {
StudentTest studentTest = new StudentTest();
studentTest.getSum(1,1);
}
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double i,double j){
System.out.println("2");
System.out.println(2);
}
}
结果:1
public class StudentTest {
public static void main(String[] args) {
StudentTest studentTest = new StudentTest();
studentTest.getSum(1,1);
}
// public void getSum(int i,int j){
// System.out.println("1");
// }
public void getSum(double i,double j){
System.out.println("2");
System.out.println(2);
}
}
结果:
2
2
public class StudentTest {
public static void main(String[] args) {
StudentTest studentTest = new StudentTest();
studentTest.getSum(1.0,1);
}
public void getSum(int i,int j){
System.out.println("1");
}
public void getSum(double i,double j){
System.out.println("2");
System.out.println(2);
}
//也是重载 和返回类型也无关 和修饰符也无关
public String getSum(double i,int j){
System.out.println("3");
System.out.println(3);
return "3";
}
}
结果:
3
3
可变个数的形参
/*
* 可变个数形参的方法
* 1. jdk5.0新增的内容
* 2.具体使用
* 2.1 可变个数形参的格式: 数据类型...变量名
* 2.2当调用可变个数形参的方法时,传入参数的个数可以是0个 1个 2个 多个
* 2.3可变个数形参方法与本类方法中方法名相同,形参个数不同的方法之间构成重载
* 2.4可变个数形参方法与本类方法中方法名相同,形参类型也想通的数组之间不构成重载 //换句话说,两者不能共存
* 2.5可变个数形参在方法的形参中,必须先声明在末尾
* 2.6可变个数习在方法的形参中,最多只能声明一个可变形参
* */
public class MethodArgsTest {
public static void main(String[] args) {
MethodArgsTest methodArgsTest = new MethodArgsTest();
methodArgsTest.show();
methodArgsTest.show("hello");
//这个可以调show(String...strs)这个重载,但是用show(String[] strs)时候会爆粗
methodArgsTest.show("hello","world");
//show(String[] strs)这个重载应该向下面这样调
methodArgsTest.show(new String[]{"AA","BB"});
//也可以这样掉show(String...strs)这个重载
methodArgsTest.show(new String[]{"AA","BB"});
}
public void show(int i){
}
//和下面的不能共存
// public void show(String[] strs){
//
// }
public void show(String...strs){
System.out.println("show(String...strs)");
}
//这个方法被注释后,也不报错,一个参数的可以走上面的可变参数的,但是这个方法有的时候会优先走这个,相当于传
//int参数时候,重载中没有,但是有double,那么进行自动类型提升下,然后走double,
public void show(String s){
System.out.println("show(String s)");
}
//编译通不过 可变形参必须放在最后
// public void show(String...strs,int i){
// System.out.println("show(String...strs)");
// }
//可以通过
public void show(int i, String... strs){
System.out.println("show(String...strs)");
}
}
show(String...strs)
show(String s)
show(String...strs)
show(String...strs)
show(String...strs)
Process finished with exit code 0
方法参数的值传递机制
/*
* 关于变量的赋值
* 如果变量是基本数据类型 此时赋值的是变量所保存的数据值
* 如果变量是引用数据类型,此时赋值的是变量所保存数据的地址值
*
* 1.形参:方法定义时,声明的小括号内的参数
* 实参:方法调用时,实际传递给形参的数据
* 2.值传递机制
* 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
* 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
*
*/
- 面试题
方法一:提前退出
方法二:重写打印流
面试题2
面试题3(结果是什么)
public class ArrayPrintTest {
public static void main(String[] args) {
int []arr=new int[10];
System.out.println(arr);
char[] arr1=new char[]{'a','v','c'};
System.out.println(arr1);
String[] arr2=new String[]{"AA","BB","CC"};
System.out.println(arr2);
}
}
[I@1b6d3586
avc
[Ljava.lang.String;@4554617c
为什么呢?上源码(哈哈)
- print(char[]) 相当于把每字符数组里面的东西都写出来了
/**
* Prints an array of characters. The characters are converted into bytes
* according to the platform's default character encoding, and these bytes
* are written in exactly the manner of the
* <code>{@link #write(int)}</code> method.
*
* @param s The array of chars to be printed
*
* @throws NullPointerException If <code>s</code> is <code>null</code>
*/
public void print(char s[]) {
write(s);
}
/*
* The following private methods on the text- and character-output streams
* always flush the stream buffers, so that writes to the underlying byte
* stream occur as promptly as with the original PrintStream.
*/
private void write(char buf[]) {
try {
synchronized (this) {
ensureOpen();
textOut.write(buf);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush) {
for (int i = 0; i < buf.length; i++)
if (buf[i] == '\n')
out.flush();
}
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
- print(Object) 把地址值输出来
/**
* Prints an object. The string produced by the <code>{@link
* java.lang.String#valueOf(Object)}</code> method is translated into bytes
* according to the platform's default character encoding, and these bytes
* are written in exactly the manner of the
* <code>{@link #write(int)}</code> method.
*
* @param obj The <code>Object</code> to be printed
* @see java.lang.Object#toString()
*/
public void print(Object obj) {
write(String.valueOf(obj));
}
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
面向对象特征之一
封装与隐藏
- 在类中私有类的属性,提供setXxx来赋值,
/*
*问题的引入:
* 当我们要创建一个类对象以后,我们可以通过对象.属性"的方式,对对象的属性进行赋值
* 这里赋值操作要受到属性的数据类型和存储范围的制约,但是除此之外,没有其他条件,但是在实际中,我们要为属性赋值加额外的条件
* 同时避免用户再使用对象.属性的方式对属性进行赋值,则需要将属性声明为私有的(private)
* -->此时,针对属性就体现了封装性.
*
* 封装性的体现
* 我们将类的属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)
* 拓展:封装性的体现:①如上②不对外暴露的私有的方法③单列模式...*/
package object1;
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
animal.legs=-10;
}
}
class Animal{
String name;
int age;
int legs;
//属性的设置
public void setLegs(int legs) {
if(legs>=0){
legs=legs;
}
else{
legs=0;
}
}
//属性的获取
public int getLegs() {
return legs;
}
public void food(){
System.out.println(name+age+legs);
}
}
权限修饰符
- 从小到大
- private
- 缺省 (default)
- protected
- public
- 四种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
- 具体的,4种权限都可以用来修饰类的内部结构、属性、方法、构造器、内部类
- 修饰类的话 只能使用缺省、public
- 总结:
- 封裝性的体现
构造器
/*
* 类的结构之三,构造器(或构造方法、constructor)的使用
* construct:建设、建造.construction:CCB constructor:建造者
*
*一、构造器的作用
* 创建对象
* 初始化对象的信息
* 二说明
* 1.如果没有显示的定义类的构造器的话,则系统默认提供一个空参的构造器
* 2.定义构造器的格式,权限修饰符 类名(形参 列表){}
* 3.一个类中定义的多个构造器,彼此构成重载
* 4. 一旦我们显式的定义了类的构造器之后,系统就不在提供默认的空参构造器
* 5.一个类中,至少会有一个构造器
* 默认的构造器权限和类是相同的权限
* */
public class PersonTest {
public static void main(String[] args) {
//创建类的对象 new+构造器
Person p =new Person("guo");
System.out.println(p.getName());
}
}
class Person{
//属性
private String name;
private String name1;
private int age;
//构造器
public Person(){
System.out.println("Person()...");
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
//方法
public void eat(){
System.out.println("ren 吃饭");
}
}
- 总结属性赋值的过程
/*
* 总结:属性赋值的先后顺序
* ①默认初始化值
* ②显式初始化 (声明时候就初始化,即int age=1)
* ③构造器中初始化
* ④通过对象.方法 或对象.属性的方式 赋值
*
* 以上操作的先后顺序 :①--②-③-④*/
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(4);
System.out.println(u1.age);
}
}
class User{
String name;
int age=1;
public User(){
}
public User(int age) {
this.age = age;
}
public void setAge(int age) {
this.age = age;
}
}
this关键字
以前形参名和属性名不重名的时候可以不用this,但是要做到见名知意,知道给谁赋值,所以引入了this,来区分形参名和属性名重名情况,其他用this也是为了避免这种类似情况.
-
this关键字的使用
-
-
this可以用来修饰:属性、方法、构造器
-
this修饰属性和方法
- this理解为当前对象 或当前正在创建的对象
- 在类的方法中,我们可以使用 this.属性 或 this.方法 的方式,调用当前对象属性或方法,通常情况下,我们都省略"this." 特殊情况下,如果方法的形参和类的属性重名时,我们必须显式的使用this.变量方式,表明此变量是属性而非形参
- 在类的构造器中,我们可以使用 this.属性 或 this.方法 的方式,调用当前正在创建的对象属性或方法,通常情况下,我们都省略"this." 特殊情况下,如果方法的形参和类的属性重名时,我们必须显式的使用this.变量方式,表明此变量是属性而非形参
-
this调用构造器
- 我们在类的构造器中,可以显式的使用"this(参数列表)",调用本类中指定的其他构造器;
- 构造器中不能通过"this(形参列表)"方式调用自己
- 如果一个类中有n个构造器,则最多有n-1构造器使用了"this(参数列表)";
- 规定:"this(参数列表)"必须声明在当前构造器的首行
- 构造器内部最多只能声明一个"this(参数列表)",用来调用其他的构造器.
构造器package acmcompute; public class PersonTest { public static void main(String[] args) { // Person p = new Person(); // p.setAge(1); // System.out.println(p.getAge()); // p.eat(); Person p2=new Person("jerry"",20); } } 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(int age) { this(); this.age = age; } public Person(String name, int age) { //调用自己空参的 //this(); this(age); //Person初始化时候,需要考虑如下1 2 3 4..(共40多行代码) this.name = name; // this.age = age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public int getAge() { return age; } public void setAge(int age) { //age = age; this.age = age; } public void eat(){ System.out.println("人吃饭"); this.sleep(); } public void sleep(){ System.out.println("人睡觉"); } }
-
-
package关键字
/*
* 一.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子包下的结构,则仍需要显式的导入
* 9.import static:导入指定类或接口中静态结构*/
MVC
面向对象特征之二:继承性
一、继承性的好处
①减少了代码的冗余,提高了代码的复用性
②便于功能的扩展
③为了之后多态的使用,提供了前提
二、继承性的格式;class A extend B{}
A:子类、派生类、subclass
B:父类、超类、基类、superclass
2.1 体现:一旦子类A继承了B以后,子类A中就获取了父类B中声明的所有的属性和方法
特别的,父类中声明为peivate的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得子类不能直接调用父类的 结构而已.
2.2子类继承父类以后,还是可以声明自己特有的属性或方法,实现功能的扩展
子类和父类的关系,不同于子集和集合关系
extend:继承关键字
三:Java中关于继承性的规定:
1.一个类可以被多个子类继承
2.类的单继承 一个类只能有一个父类
3.子类是相对的概念
4.子类直接继承的父类,称为,直接父类,间接继承的父类称为,间接父类
5.子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
四、1.如果我们没有显式的声明一个类的父类的话,则此类继承与java.lang.Object类
2.所有的类(除java.lang.Object类之外)都直接或间接的继承与java.lang.Object类
3.意味着,所有的java类具有java.lang.Object类声明的功能
方法的重写(override)
/*
* 方法的重写
* 1.重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作
* 2.应用:重写以后,当创建子类对象以后,通过子类对象调用父类中同名同参的方法时,实际执行的是子类重写父类的方法
*
* 面试题:区分方法的重载与重写
* 3.重写的规定:
* 方法的声明:权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
* 方法体
* }
* 约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法
* ①子类重写的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
* ②子类重写的方法的权限修饰符不小于父类中权限修饰符 可以等于
* >特殊情况:子类不能重写父类中声明为private权限的方法 //改了以后的就不是重写了,不报错,是另一个方法了,方法类型就可以随意定了
* ③返回值类型:
* ->父类被重写的方法的返回值是void,则子类重写的方法的返回值类型也只能是void
* ->父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类 这是对引用数据类型来讲的
* ->父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型是相同的数据类型(必须也是double)
* ④子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理再说)
* ******************************************************************************
* 子类和父类中同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static(不是重写)
* */
四种权限修饰
- TA包下类
package T1.TA;
public class Order {
private int orderPrivate;
int orderDefault;
protected int orderProtected;
public int orderPublic;
private void methodPrivate(){
orderPrivate=1;
orderDefault=2;
orderProtected=3;
orderPublic=4;
}
void methodDefault(){
orderPrivate=1;
orderDefault=2;
orderProtected=3;
orderPublic=4;
}
protected void methodProtected(){
orderPrivate=1;
orderDefault=2;
orderProtected=3;
orderPublic=4;
}
public void methodPublic(){
orderPrivate=1;
orderDefault=2;
orderProtected=3;
orderPublic=4;
}
}
package T1.TA;
public class OrderTest {
public static void main(String[] args) {
Order order=new Order();
order.orderDefault=1;
order.orderProtected=2;
order.orderPublic=3;
order.methodDefault();
order.methodProtected();
order.methodPublic();
//同一个包中的其他类,不可以调用Order类中的私有属性、方法
// order.orderPrivate=4;
// order.ordermethodPrivate():
}
}
- TB包下的类
package T1.TB;
import T1.TA.Order;
public class SubOrder extends Order {
public void method(){
orderProtected=1;
orderPublic=100;
methodProtected();
methodPublic();
//在不同包的子类中,不能调用Order类中声明为private和缺省的结构
// orderDefalut=3;
// orderPrivate=4;
// methodDefault();
// methodPrivate():
}
}
package T1.TB;
import T1.TA.Order;
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
order.methodPublic();
order.orderPublic=5;
System.out.println(order.orderPublic);
//不同包下的普通类(非子类) 要使用Order类,不可以调用声明为private,缺省、protected属性、方法
// orderDefault=2;
// orderProtected=3;
// orderPublic=4;
//
// methodPrivate();
// methodProtected();
// methodPublic();
}
public void show(Order order){
order.methodPublic();
order.orderPublic=5;
System.out.println(order.orderPublic);
//不同包下的普通类(非子类) 要使用Order类,不可以调用声明为private,缺省、protected属性、方法
// orderDefault=2;
// orderProtected=3;
// orderPublic=4;
//
// methodPrivate();
// methodProtected();
// methodPublic();
}
}
super关键字
/*
* super关键字的使用
* 1.super理解为:父类的
* 2.super可以用来调用:属性、方法、构造器
* 3.super的使用
* 3.1我们可以在子类的方法或构造器中,通过使用super.属性 或 super.方法 的方式,显式的调用父类中声明的属性或方法
* ,但是,通常情况下,我们可以省略super
* 3.2特殊情况,当子类和父类定义了同名的属性,我们要想在子类中调用父类中声明的属性,则必须显式的
* 使用super.属性的方式,表明调用的是父类中的声明属性
* 3.3特殊情况,当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的
* 使用super.方法 的方式,表明调用的是父类中被重写方法*/
- 测试 super调用方法和属性
Person类
package T1.TC;
public class Person {
String name;
int age=18;
int id=1001;//身份证号
public Person(){
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
public void eat(){
System.out.println("人:吃饭");
}
public void work(){
System.out.println("人:走路");
}
}
Student类
package T1.TC;
//子父类都有属性id,在继承时并不会被覆盖,都存在
public class Student extends Person{
String major;
int id=1002;//学号
public Student(String major) {
this.major = major;
}
public Student() {
}
@Override
public void eat() {
System.out.println("学生:多吃营养的事物");
super.eat();
}
@Override
public void work() {
System.out.println("学生:学习知识");
super.work();
}
public void show(){
System.out.println("name="+super.name+",age="+this.age);
System.out.println("id="+id);
System.out.println("superid="+super.id);
}
}
SuperTest
package T1.TC;
public class SuperTest {
public static void main(String[] args) {
Student student = new Student();
student.show();
student.work();
}
}
运行结果:
name=null,age=18
id=1002
superid=1001
学生:学习知识
人:走路
- super调用构造器
/*
super构造器
1.我们可以在子类构造器中显式的使用super(形参列表)的方式,调用父类中声明的指定构造器
2.super(形参列表)的使用,必须声明在子类构造器的首行
3.我们在类的构造器中,针对于this(形参列表 ) 或 super(形参列表) 只能2选1
4.在构造器的首行,没有显式的声明this(形参列表) 或 super(形参列表) 则默认调用的是父类的构造器
5.在类的多个构造器中,至少有一个类的构造器中使用了super(形参列表),调用父类的构造器
*/
public Student(String name, int age, String major, int id) {
super(name, age);
this.major = major;
this.id = id;
}
子类对象的实例化
/*
* 子类对象实例化的全过程(继承性)
* 子类继承父类以后,就获取了父类中声明的属性或方法
* 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
* 2.从过程上来看:
* 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用父类的构造器,进而调用父类的构造器
* 直到调用了java.lang.Object类中空参的构造器为止.正因为加载过所有的父类的结构,所以才可以看到内存中父类的结构,子类对象才可以考虑进行调用.
*
* 明确;虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象
*
* */
面向对象特征之三:多态性
/*
* 面相对性特征之三 :多态性
* 1.理解多态性,可以理解为一个事物的多种形态
* 2.何为多态性:
* 对象多态性 父类的引用指向子类的对象(子类对象赋给父类的引用)
* 3.多态的使用,虚拟方法调用
* 有对象多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类中重写父类的方法
*
* 总结:编译 看左边 运行 看右边
* 4.多态性使用前提:①类的继承性 ②方法的重写
* 5.对象的多态性,只适用于方法,不适用于属性(属性编译和运行都看左边)
*
* 其实多态就是减少调用方法的重载
*/
引用毕向东教程的一段话:
1.成员变量:编译和运行都参考左边。
2.成员函数(非静态):编译看左边,运行看右边
3.静态函数:编译和运行都看左边。
package duotai;
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){
animal.eat();
animal.shout();
}
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal {
public void eat(){
System.out.println("狗:吃骨头");
}
public void shout(){
System.out.println("狗:汪汪汪");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵喵喵");
}
}
面试题 多态是编译时行为还是运行时行为?
* 虚拟方法调用(多态情况下)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,类根据赋给它的不同子类对象,动态调用属于子类的方法.这样的方法调用在编译期是无法确定的.
Person e =new Student();
e.getInfo(); //调用student类的getInfo方法
* 编译时类型运行时类型
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法--动态绑定
是运行时行为 举个经典的例子
package duotai.O1;
import java.util.Random;
class Animal{
public void eat(){
System.out.println("动物吃");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃");
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗吃");
}
}
class Sheep extends Animal{
public void eat() {
System.out.println("羊吃草");
}
}
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();
}
}
- 不是说重载只发生在同一个类中么?
我认为这个子类重载父类同名不同参数的父类方法不矛盾,因为子类继承了父类的方法,是属于本类的,因此在子类中再定义一个方法重载父类的也是可以的.
-
重载:不表现为多态性
-
重写:表现为多态性
-
重载是编译时确定,重写是运行时确定
思考
- 为什么super(…) 或this(…)调用语句不能在一个构造器中出现?
因为他俩都必须出现在构造器的首行,所以不可以同事出现
- 为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现呢?
无论通过哪个构造器创建子类对象,需要保证先初始化父类
目的:当子类继承父类后,"继承"父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化
3.强调说明:
虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象.
补充
Person p=new Man()
p不能调用子类所特有的方法、属性, 编译时,p是Person类型
有了对象多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法.子类特有的属性和方法不能调用,但是东西还是是有的.
- 如何才能调用子类特有的属性和方法
//Man m1=p; //会报错 不可以
使用强制类型转化符可以
Man m1=(Man)p;
//使用强转是,可能出现ClassCastException的异常.
/*因此引入了一个关键字 instanceof:
a instanceof A:判断对象a是否是类A的实例,如果是,返回true,如果不是,返回false.
*/
if(p instanceof woman){
woman w1=(woman)p;
w.goShopping();
System.out.println("*****woman****")
}
使用情景:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,返回true就进行向下转型,如果返回false,不进行向下转型
练习
- 问题一: 编译时通过,运行时不通过
Person p3=new Woman();
Man m3=(Man)p3;
Person p4=new Person();
Man m4=(Man) p4;
- 问题二 编译通过,运行时也通过
Object obj=new Woman();
Person p=(Person)obj;
- 问题三:编译不通过
Man m5=new Woman();
- 类变量和实例变量 局部变量区别
- 实例变量 和类变量都是成员变量,声明在方法外,类中
- 类变量用static修饰,实例变量没有static修饰
- 局部变量是声明在方法内的.
- java中静态方法可以被重载和重写么?
静态方法可以被重载,但是不可以被重写,在子类中写了他会被认为是另一个全新的方法,不是对静态方法的重写.虚拟机加载静态方法的时候已经确定好了.