封装和隐藏
四种访问权限修饰符
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | Yes | |||
(缺省) | Yes | Yes | ||
protected | Yes | Yes | Yes | |
public | Yes | Yes | Yes | Yes |
对class的权限修饰只可以用public和缺省
构造器
- 如果没有显示自定义构造器,系统会提供默认空参构造器
- 一旦显示自定义构造器,系统不会再提供默认空参构造器
- 父类的构造器不会被子类继承
- 构造器重载,参数列表必须不同
拓展:JavaBean
- JavaBean是一种Java语言写成的可重用组件
- 所谓javaBean,是指符合如下标准的Java类:
(1)类是公共的
(2)有一个无参的公共构造器
(3)有属性,且有对应的get、set方法- 用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变
拓展:UML类图
this关键字
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器
- 构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
super关键字
- 子类中所有的构造器默认都会访问父类中空参数的构造器
- 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没 有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须 放在子类构造器的首行 |
关于this和super构造器只能二选一并且要放在第一行:
首先,必须在构造器的第一行放置super或者this构造器,否则编译器会自动地放一个空参数的super构造器的,其他的构造器也可以调用super或者this,调用成一个递归构造链,最后的结果是父类的构造器(可能有多级父类构造器)始终在子类的构造器之前执行,递归的调用父类构造器。无法执行当前的类的构造器。也就不能实例化任何对象,这个类就成为一个无为类。 从另外一面说,子类是从父类继承而来,继承了父类的属性和方法,如果在子类中先不完成父类的成员的初始化,则子类无法使用,应为在java中不允许调用没初始化的成员。在构造器中是顺序执行的,也就是说必须在第一行进行父类的初始化。而super能直接完成这个功能。This()通过调用本类中的其他构造器也能完成这个功能。 因此,this()或者super()必须放在第一行。
只能二选一,因为第一行只有一个。另外,如果调用了this,那this里面必然又调用了super或this,这样追溯上去,总有一个构造器是调用了super,这样一路继承下来。所以没必要再调super了。
多态
- 体现:父类的引用指向子类的对象,如,
Student
是继承自Person
的:
Person p = new Student();
Object o = new Person();
o = new Student();
- Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简单来说:编译时看左边,运行时看右边。
- 看左边:看的是父类的引用(
父类中不具备子类特有的方法
) - 看右边:看的是子类的对象(
实际运行的是子类重写父类的方法
)
- 看左边:看的是父类的引用(
- 若编译时类型和运行时类型不一致,就出现了对象的多态性
- 子类的对象可以替代父类的对象使用
- 一个变量只能有一种确定的数据类型
- 一个引用类型变量可能指向多种不同类型的对象
- 子类可看作是特殊的父类,所以父类类型的引用可以指向子类的对象,称为向上转型
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中特有的属性和方法。因为属性是在编译时确定的,编译时为父类类型,所以没有子类特有的成员变量。 如下:
Student s = new Student();
s.school = "XU"; //合法,Student类有school成员变量
Person p = new Student();
p.school = "XU"; //非法,Person类没有school成员变量
p.setName("XU"); //合法,Person类和Student类都有setName方法,但调用的是Student类的setName方法
如果是这么一种情况:
Person
有set(String name)
方法,Student
有set(String name, int number)
方法,那么以下情况:
Person p = new Student();
p.setName("XU"); //合法,调用的是Person类的setName。如果子类重写了此方法,则调用的是子类的setName方法
p.setName("XU", 10); //非法,子类特有的方法,调用不了
Student s = new Student();
s.setName("XU"); //合法,调用的是Person类的setName。如果自己重写了此方法,则调用自己的
s.setName("XU", 10); //合法,调用自身方法
编译时p
为Person
类型,而方法调用是在运行时确定的,所以调用的是Student
类的setName
方法,这叫做动态绑定
再议重载与重写
- 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数列表,对同名方法的名称做修饰。对于编译器而言,这些同名方法构成了不同的方法。它们的调用地址在编译期就绑定了。Java中子类可以重载父类同名不同参的方法。对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为静态绑定。
- 重写,指有多个同名方法,并且参数列表相同,但方法体不同。*(对编译器而言它们是相同的方法?)*Java中子类可以重写父类同名同参的方法。只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为动态绑定
- 方法的重写是多态的前提之一
多态小结
- 方法:
- 编译时:要查看引用变量所声明的类中是否有所调用的方法
- 运行时:调用实际
new
的对象所属的类中的重写方法
- 属性:不具备多态性,只看引用变量所声明的类,即声明谁,引用谁。
- 在处理多态时,可以从父类向子类推进,用instanceof先检查类型,然后再进行相应类的操作,如下:
if(p instanceof Person)
//进行Person类方法
if(p instanceof Student)
//进行Student类方法
对象类型转换
对象类型转换是多态的一个体现
- 基本数据类型的转换
- 自动类型转换,如
double d = 12.0f
- 强制类型转换,如
int a = (int)1200L
- 自动类型转换,如
- 对Java对象的强制类型转换:
- 从子类到父类的类型转换可以自动进行
- 从父类到子类的类型转换必须通过强制类型转换实现
- 无继承关系的引用类型之间的转换是非法的
- 在强制转型之前可以使用
instanceof
测试一个对象的类型
练习:多态是编译时行为还是运行时行为?如何证明?
//运行时行为
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(); //运行时才能知道具体创建对象
}
}
下面记录一些自己实验出来的一些小结论:
//1.静态方法无法重写
class Person1{
static String name = "Person";
public static String getName(){
System.out.println(name);
return name;
}
}
class Student1 extends Person1{
static String name = "Student";
public static String getName(){//此处的重写是无效的,即使创建Person对象指向Student,调用getName,输出和返回的依然是Person
System.out.println(name);
return name;
}
}
public class Test {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3);//方法被重写,输出的sub_1
//最下面的add(int a, int b, int c)不会被调用,因为参数列表变了,它是重载不是重写,不符合多态要求
Sub s = (Sub)base;
// s.add(1, 2, 3);//如果没有最下面重载的add(int a, int b, int c)方法,这个语句会报错,
// 因为此时是Sub对象,参数形式不符合Sub的add(int a, int[] arr)要求
//作为重载方法,add(1, 2, 3)的参数形式相比于add(int a, int... arr)
//更符合add(int a, int b, int c)。优先调用确定个数参数方法,所以调用add(int a, int b, int c)
//所以这里输出的是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) {//编译器认为int...和int[]是一样的形参(虽然实际上不一样),所以这也算是方法的重写
System.out.println("sub_1");
}
// public void add(int a, int b, int c) {//这个方法不是重写,是重载
// System.out.println("sub_2");
// }
Object类
主要结构
No. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造器 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash 码 |
4 | public String toString() | 普通 | 转换为字符串 |
==与equals方法
==
:- 基本数据类型比较值:只要两个变量的值相等,即为
true
- 引用数据类型比较引用(比较地址值,是否指向同一个对象):只要指向同一个对象,即为
true
- 用
==
进行比较时,两边的数据类型必须兼容(可自动转换的基本数据类型除外,如1 == 1.0
)
- 基本数据类型比较值:只要两个变量的值相等,即为
equals()
:- 只能比较引用类型,比较是否指向同一个对象
- 对
File
、String
、Date
及包装类来说,它们重写过equals
方法,比较的是类型及内容,而不考虑是否是同一个对象 - 自定义类可以重写
equals
方法,一般都重写,用于比较两个对象的内容是否相等
==和equals的区别
==
既可以比较基本类型也可以比较引用类型,对于基本类型就是比较值,对于引用类型就是比较内存地址equals
是属于java.lang.Object
类里的方法,如果该方法没有被重写过,默认功能和==
一样;一般使用都是重写过的,比如String
等类型,比较的是内容- 具体要看自定义类里有没有重写
equals
方法来判断。通常情况下,重写equals
都会比较类中的相应内容是否相等
toString方法
- 在
Object
中,toString
返回值是String
类型,返回类名和它的引用地址 - 在进行
String
与其他类型数据连接操作时,自动调用toString
方法 String
和一些常用类已经重写了toString
方法,直接输出内容- 自定义类可以重写
toString
方法,一般都重写,把需要的内容转换为字符串输出
包装类
- 针对八种基本数据类型定义相应的引用类型—包装类,使它们有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
包装类的使用
- 字符串转换为基本数据类型:
int i = new Integer("12");
Float f = Float.parseFloat("12.1");
- 基本数据类型转换为字符串
String fStr = String.valueOf(2.34f);
String intStr = 5 + ""
这里主要记一下parseXxx
和valueOf
两种方法
思考题:
1.考虑下面两段程序的输出:
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);
第一段程序:输出:1.0
。因为这个三元运算符后面两个运算对象需要类型统一,所以Integer
类型自动提升为Double
类型。Object o1 = new Integer(1)
,多态,调用的是Integer
的toString
方法,所以输出内容
第二段程序:输出1
,不存在类型转换
2.思考下面程序的输出
public void method1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);
Integer m = 1;
Integer n = 1;
System.out.println(m == n);
Integer x = 128;
Integer y = 128;
System.out.println(x == y);
}
包含的知识点:Integer
内部提前定义了IntegerCache
结构,其中有一个Integer[]
,保存了-128~127
范围的整数。如果使用自动装箱的方式给Integer
赋值这个范围的数,可以直接使用数组中的元素,不需要再使用new
方法创建。出了这个范围的数,依然需要使用new
方法创建
所以三个输出分别是:false
,true
,false