一、类和对象
定义类
java的类名由一个或者多个有意义的单词组合而成,每个单词的首字母大写,其他的全部消协,并且单词之间没有分隔符。
定义类:[修饰符] class 类名
{
//成员变量(field)
//方法(method)
//构造器(constructor)
}———类体
成员变量:用于定义该类或者实例的所包含的状态数据。
语法格式: [修饰符] 类型 成员变量名 [= 默认值];
—修饰符:可以省略 public | protected | private、static、final、(transient:序列化相关)
—类型: 可以是8个基本类型,可以任意的引用类型(数组、String、JDK任何类、自己定义的类)
—成员变量名:从语法角度来看, 变量名只要是一个合法的标识符即可。
从程序可读性(习惯)来要求, 变量名应该是一个或多个有意义的单词连缀而成,中间不要分隔符,
第一个单词的首字母小写,后面每个单词首字母大写 —— 驼峰写法。
常量全部单词大写,单词之间用_隔开。
方法:用于定义该类或者实例所包含的功能实现和行为特征。
[修饰符] 返回值类型 方法名(形参列表) —— 这行叫方法签名。
{
//方法体:定义变量(包括数组)、变量赋值、流程控制、数据结构
}
正常的方法 = 方法签名 + 方法体
—修饰符: 可以省略public | protected | private 、static、final | abstract
—返回值类型:可以是8个基本类型,可以是任意的引用类型(数组、String、JDK任何类、自己定义的类)
该返回值类型,指定了调用该方法将会得到一个Xxx类型的值,表明我们的程序可以从调用方法中得到xxx类型的值。
如果调用方法后,不会得到任何的东西,就将该方法定义为没有返回值,用void来声明返回值类型。
如果你声明了返回值类型,方法体内必须用有效return 语句来返回【返回值】
—方法名:从语法角度来看,方法名只要是一个合法的标识符即可。
从程序可读性(习惯)来要求,方法名应该用驼峰写法。
建议:方法一般用于描述类的行为,因此方法名通常使用动词,只定义项目中感兴趣的。
—形参列表:形参类型1,形参类型2,形参类型3,。。。。。
每个形参都满足”形参类型 形参名”的格式;多个形参之间用逗号隔开
构造器:用于构造该类的实例。
【如果你没有为类写构造器,系统会默认为该类提供一个无参数的构造器】
构造器定义、与方法定义有点相似。主要体现为2个区别:
1. 构造器定义无需声明返回值类型,返回值类型总是当前类,不需要显式使用return。
2 . 构造器的名字必须与类名相同。
[修饰符] 构造器名 (形参列表)
{
//代码:定义变量(包括数组)、变量赋值、流程控制、数据结构
}——构造器体
——修饰符 - 可以省略 public | protected | private
——构造器名 - 必须与类名相同。
—— 构造器体 - 与方法体类似,都是放各种执行性的代码。
static关键字
static修饰的成员表示他是属于类的,类变量类方法。也叫静态。静态成员不能直接访问非静态成员。
static真正的作用是用来区分成员变量,方法,内部类,初始化块属于类本身还是属于实例。有static修饰的属于类本身,没有static修饰的成员数与类的实例
类可做如下事情:
1.定义变量 ,
所有类都属于引用类型,都可用于声明变量
2.调用static修饰方法或static修饰的变量
3.创建对象
new 构造器(参数)
4.派生子类
实例可做如下事情:
- 可调用的方法 ——驱动它去完成特定行为。
- 访问field —— 获取它的状态。
二、对象
对象可做如下事情:
1.调用无static修饰的成员变量
2.调用无stastic修饰的方法
对象的产生和使用
Person p=new Person();//定义p变量,并为p变量赋值,person是对象
p.say("你真棒");
p.name(“李刚”);//直接给成员变量赋值
类定义的是多个实例的特征,因此类不是一种具体存在,实例才是具体存在
对象和引用
堆里面的对象可以有多个引用。
Person p1=p;
这时p1和p是引用变量都指向同一个对象。
如果希望通知垃圾回收机制回收某个对象,把引用变量赋值为null
this
this 关键字总是代表调用该方法的对象.
- 1.如果在构造器中,this代表该构造器正在初始化的对象。
- 2.如果在方法中, this代表调用该方法的对象。
在方法中,谁调用该方法,this就代表谁。
在构造器中,构造器正在初始化谁,this就代表谁。
无论是构造器、还是方法,它们中出现的this引用,只有等到调用该构造器、调用该方法时,才能确定代表谁
如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀
public class Dog {
public void jump()
{
System.out.println("正在执行jump方法");
}
public void run()
{
//如果想在这个方法里面调用另一个方法
//如果没有this,则必须再创建一个对象
//有了this,就不需要创建了,因为this就是指向调用该方法的对象
this.jump();
jump();
}
【注:】 this 不能用在有static 修饰的方法中.static修饰的方法属于类,不属于对象
static修饰的方法,是用类来直接调用的,因此不需要再创建一个对象,因此也就不使用this,因此static对象不能访问非静态成员
public class StaticAccessNonStatic {
public void info()
{
System.out.println("简单的方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new StaticAccessNonStatic().info();
}
}
大部分时候,普通方法访问其他方法,成员变量无需使用this,但是如果方法里面有局部变量和成员变量同名,但是又需要在方法里面访问,则必须适应this
this也可以用于构造器作为默认引用。代表构造器正在初始化的对象。
public class ThisInConstructor {
//定义一个名为foo的成员变量
public int foo;
public ThisInConstructor ()
{
// 在构造器中又定义了一个foo
//this代表正在初始化的对象,把他的foo成员变量设为6
int foo=0;
this.foo=6;
}
this还可以作为普通方法的返回值,表示返回调用该方法的对象
三、方法
方法的所属性
★ 一旦将一个方法定义在一个类里,如果用static修饰了,这个方法属于该类本身,否则属于这个类的实例.
有static修饰,属于类;没有static修饰,属于对象。
★ 方法不能独立执行,必须要有调用者.(如:类.方法、对象.方法)
方法必须由属主调用。谁的方法,谁调用。
没有static的方法,属于对象,由对象调用。
有static的方法,属于类,由类调用。
★ 方法不能独立定义,只能定义在类里.
方法要么属于一个类(有static),要么属于一个对象(无static)
调用方法时,总是需要主调(主语调用者),如果方法有static修饰,要用类做主调。
如果方法没有static修饰,要用对象做主调。
在有些时候,可以省略主调——
如果在没有static修饰的方法中调用另一个方法,默认可以省略this.前缀。
如果在没static修饰的方法中调用另一个没static的方法,默认可以省略this.前缀。
如果在没static修饰的方法中调用另一个有static方法,默认可以省略 类名.前缀。
如果在有static修饰的方法中调用另一个有static方法,默认可以省略 类名.前缀。
如果在有static修饰的方法中调用另一个没static方法——这种调用是不允许的。
方法的参数传递机制
参数传递方式是一种值传递方式。也就是实参的副本保存在形参中。如果形参是基本类型,那么方法中对形参的修改不会改变实参;如果形参是引用类型,仍然是指传递,由于引用类型是指针,保存的是地址,所以方法中对形参的修改会改变实参的值,但是如果在方法中把形参的引用赋值为null,则实参不会改变。
class DataWrap
{
int a;
int b;
}
public class ReferenceTransferTest
{
public static void swap(DataWrap dw)
{
// 下面三行代码实现dw的a、b两个成员变量的值交换。
// 定义一个临时变量来保存dw对象的a成员变量的值
int tmp = dw.a;
// 把dw对象的b成员变量值赋给a成员变量
dw.a = dw.b;
// 把临时变量tmp的值赋给dw对象的b成员变量
dw.b = tmp;
System.out.println("swap方法里,a成员变量的值是"
+ dw.a + ";b成员变量的值是" + dw.b);
// 把dw直接赋为null,让它不再指向任何有效地址。
dw = null;
}
public static void main(String[] args)
{
DataWrap dw = new DataWrap();
dw.a = 6;
dw.b = 9;
swap(dw);
System.out.println("交换结束后,a成员变量的值是"
+ dw.a + ";b成员变量的值是" + dw.b);
}
}
运行结果:原本a=9,b=6,交换后a=9,b=6
参数个数可变的方法
在最后一个形参的类型后面加上"...", 表示形参可以接受多个参数。本质上就是一个数组类型的形参。传入的实参可以是多个参数,也可以是一个数组。
但是参数个数可变的形参必须处于形参列表的最后,且只能有一个。
如果形参是数组的话,传递的方式是
test(new String[]{"abs","cps"});
如果形参时参数可变的,传递方式除了上边的方式,还可以是
test("abs","cds");
public class Varargs {
//定义一个参数可变的方法
public static void test(int a,String ...books)
{
//books被当作数组处理
for(String tmp:books)
{
System.out.println(tmp);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
test(4,"ans","csd");
}
}
public class Recursive {
public static int fn(int n)
{
if(n==1)
{
return 1;
}else if(n==2)
{
return 2;
}
else
{
//方法中调用自身
return 2*fn(n-1)+fn(n-2);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(fn(10));
}
}
递归方法
所谓递归:就是指,在方法里再次调用自身。
递归,相当于一个隐式的循环。递归,也要有一个结束点。
比如有一个数列: f(1) = 2; f(2)=5;
f(n) = f(n+2) - 2 * f(n+1);
计算f(10)的值。
【注意:】递归一定要有结束点。
可以考虑对 f(n) = f(n+2) - 2 * f(n+1) 公式进行变换。定义一个新的N, 让N = n + 2.
公式可改为: f(N-2) = f(N) - 2 * f(N-1)
f(N) = 2 * f(N-1) + f(N-2)
public class Recursive
{
public static int fn(int n)
{
if (n == 0)
{
return 1;
}
else if (n == 1)
{
return 4;
}
else
{
// 方法中调用它自身,就是方法递归
return 2 * fn(n - 1) + fn(n - 2);
}
}
public static void main(String[] args)
{
// 输出fn(10)的结果
System.out.println(fn(10));
}
}
方法的重载
“两同一不同”
同一个类中方法名相同,参数列表不同。
注意被重载的方法里面包含了参数可变的形参。
public class OverloadVarargs {
public void test(String msg)
{
System.out.println("只有一个形参的test方法");
}
public void test(String ... books)
{
System.out.println("多个形参的test方法");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
OverloadVarargs olV =new OverloadVarargs();
olV.test("aa");
olV.test("bb","cc","dd");
olV.test();
olV.test(new String[]{"aa"});
}
}
Java调用方法时,要确定一个方法,需要3个东西:
1. 调用者
2. 方法名。
3. 参数列表。
五、 成员变量和局部变量
↗ 类变量(有static修饰),属于类,用于描述类本身的状态
成员变量
↗ ↘ 实例变量,属于各个实例,用于描述实例的状态
变量
↗ 方法局部变量: 在方法中声明的变量
↘
局部变量 → 代码块的局部变量, 在代码块中声明的变量。
↘ 形参
成员变量:变量用于描述某个类、对象的固有信息,
需要一个变量来保存该类或者实例运行的状态信息
需要变量在某个类的多个方法之间进行共享
类变量,用描述类的状态;实例变量,用于描述实例的状态。
如果要访问类变量(获取值, 也包括对它赋值),必须指定类作为主调。
如果要访问实例变量(获取值, 也包括对它赋值),必须指定的对象作为主调。
★ 成员变量可以不指定初始值.
如果程序员没有为成员变量指定初始值,系统会为之分配默认的初始值,规则为,
与动态初始化数组时,数组元素的初始化规则完全相同。
类变量,并不是属于对象,而是属于类本身,当系统初始化类时,就会为类变量分配内存、并指定初始值。
垃圾语法:【Java允许通过对象来访问类变量、也可调用类方法】
以后你们编程时,永远不准通过对象来访问类变量、也不准通过对象来调用类方法。应该用类名。
但以后,如果你在面试、考试...遇到有人通过对象来访问类变量、通过对象来调用类方法,
首先你先用把对象换成类名——这才是本质。
成员变量的作用域:
类变量:从类加载、并初始化开始有效, 直到该类被销毁时无效。
如果是官方JVM,JVM不退出,类信息就不会被销毁。
尽量少用static修饰的变量。
实例变量:有多少个对象,就有多少个实例变量。每个对象都会持有自己的实例变量。
从对象加载、并初始化开始有效, 直到该对象被销毁时无效。
只要一个对象没有引用变量指向它,它就变成垃圾,垃圾回收器将会在接下来时间里回收它。
★ 局部变量
1. 局部变量必须由于程序员显式赋值。
如果程序员没有赋值,该局部变量不能被使用。
2. 局部变量的作用域很短, 所有局部变量都保存在各自的方法栈区中。
形参:方法结束,就失效。
方法局部变量:从声明它开始有效,方法结束,就失效。
代码块局部变量:从声明它开始有效,代码块结束,就失效。
总则:优先使用作用域更小的变量,好处:
1. 节省内存。
2. 更符合程序“高内聚、低耦合”的原则。
必须先给方法局部变量和代码块局部变量现实初始化,即指定初始值。否则不可以访问。
一个类不能有两个同名的成员变量,一个方法也不能有两个同名的方法局部变量。但是两个代码块里面的代码块局部变量可以同名。
如果先定义代码块局部变量,后定义方法局部变量。代码块和方法里面的局部变量也可以同名。
允许局部变量和成员变量同名。此时局部变量覆盖成员变量,如果想访问成员变量,要用this。
public class VariableOverrideTest
{
// 定义一个name实例变量
private String name = "李刚";
// 定义一个price类变量
private static double price = 78.0;
// 主方法,程序的入口
public static void main(String[] args)
{
// 方法里的局部变量,局部变量覆盖成员变量
int price = 65;
// 直接访问price变量,将输出price局部变量的值:65
System.out.println(price);
// 使用类名作为price变量的限定,
// 将输出price类变量的值:78.0
System.out.println(VariableOverrideTest.price);
// 运行info方法
new VariableOverrideTest().info();
}
public void info()
{
// 方法里的局部变量,局部变量覆盖成员变量
String name = "孙悟空";
// 直接访问name变量,将输出name局部变量的值:"孙悟空"
System.out.println(name);
// 使用this来作为name变量的限定,
// 将输出name实例变量的值:"李刚"
System.out.println(this.name);
}
}
成员变量的初始化和内存中的运行机制
类变量和成员变量都在堆中保存。只是不再同一个地方。成员变量属于实例,类变量属于类。
局部变量的初始化和内存中的运行机制
局部变量保存在栈中,不需要垃圾回收,占的内存很小。直到被付初值才会被分配内存
变量的使用规则
成员变量的使用情形
- 某个类或者实例的固有信息。如果个个实例都是一样的,用类变量。如果不是一样的,用实例变量。
- 保存该类或者实例运行时候的状态信息。
- 多个方法都要用到。
-
public class ScopeTest1 { // 定义一个类成员变量作为循环变量 static int i; public static void main(String[] args) { for ( i = 0 ; i < 10 ; i++) { System.out.println("Hello"); } } }
类变量 实例变量 方法局部变量 代码块的局部变量 形参
需要指定初始值 × × √ √ 由调用者负责传入值
何时生效? 类初始化 对象初始化 声明该变量 声明该变量 整个方法
何时销毁 类消亡 对象消亡 方法结束 代码块结束 方法结束
占几块内存 1个 有多个对象 方法执行几次 方法执行几次 方法执行几次
有多少实例变量 就有多少变量 就有多少变量 就有多少变量
六、 实现良好的封装
理解封装
通过访问控制符把该隐藏的隐藏起来,该暴露的暴露出来。
使用访问控制符
private : 类访问权限。
如果一个成员(field、方法、内部类、构造器)用private修饰,表明该成员只能在当前类中被访问。
【彻底隐藏】
default : 包访问权限。
如果一个成员(field、方法、内部类、构造器)不用访问控制符修饰,
表明该成员只能在当前类或当前包中被访问。
【部分隐藏】
protected: 子类访问权限
如果一个成员(field、方法、内部类、构造器)用protected访问控制符修饰,
表明该成员可以在当前类、当前包、该类的子类中被访问。
【部分暴露】
public: 公开访问权限
如果一个成员(field、方法、内部类、构造器)用public访问控制符修饰,
表明该成员可以在任意地方被访问。
【彻底暴露】
关于访问控制符的使用
- 绝大部分成员变量都用private,只有少部分用static,类似全局变量的成员变量用public。除此之外,辅助实现该类的其他方法的工具类也用private。
- 如果某个类主要做其他类的父类,则用protected.
- 构造器用public。大部分外部类用public。
package和import
Java建议,包名,用公司的 域名倒写.项目名 —— 包名应该是所有字母小写。
要一个类加包,要做如下两件事情:
- 在源代码中使用package定义包。
- 把生成的class文件放在对应的文件结构下。
如果一个类位于指定包下,那么该类的完整类名应该 包名+类名, 光写类名是错的。
以后用该类时,就需要写包名+类名
为了避免在源代码中总要写包名+类名,就可用import来导入包。
import有两种格式:
import java.util.*; *只能代表类,用于表示导入指定包的所有类。
import 包名.类名 —— 只导入一个类。
Java源代码默认会 import java.lang.*;
package ,import
同一个包下的类可以自由访问,但是子包里面的还是必须要写全名
import可以导入包下面的全部类,子包单独import
,import static
import static - 可以不写类名。
import static可用于导入指定类的指定static Field,或static方法。
也可用于导入指定类的全部static修饰成员。
import static语句中的*只能代表指定类的静态成员。
import的作用是在源代码中省略写包名。
import static的作用是在源代码中省略写类名。
java 常用包
七、 构造器的作用和构造器重载
使用构造器执行初始化
构造器的作用: 用于初始化对象。
必须通过new 构造器来调用它。
所有类,都有构造器!
如果我们为类写了构造器,自然就有构造器。
如果我们没有写构造器, 系统会自动提供一个无参数的构造器。
public class ConstructorTest
{
public String name;
public int count;
// 提供自定义的构造器,该构造器包含两个参数
public ConstructorTest(String name , int count)
{
// 构造器里的this代表它进行初始化的对象
// 下面两行代码将传入的2个参数赋给this代表对象的name和count实例变量
this.name = name;
this.count = count;
}
public static void main(String[] args)
{
// 使用自定义的构造器来创建对象
// 系统将会对该对象执行自定义的初始化
ConstructorTest tc = new ConstructorTest("疯狂Java讲义" , 90000);
// 输出ConstructorTest对象的name和count两个实例变量
System.out.println(tc.name);
System.out.println(tc.count);
}
}
构造器重载
方法重载: 2同1不同
构造器重载与方法重载几乎是相似的:构造器名必然相同;形参列表不同。
通过this调用另一个重载的构造器
this关键词有两个作用:
1. this引用 —— 如果在构造器中,代表构造器正在初始化的对象。
如果在方法中,代表正在调用该方法的对象。
this引用总是代表该类的实例。
2. this调用:
只能用在构造器、而且必须是构造器中第一条代码。
this调用总是调用当前类中重载的构造器。this调用到底调用哪个构造器,取决于传入的参数值。
this调用更好! 因此它可以直接复用已有的构造器。
1. 代码更简洁。 2. 更有利于以后维护。提高了可维护性。
八、继承
面向对象的3大特征: 封装、继承、多态
继承的特点
Java的继承是一种类与类之间关系,是一种“由一般到特殊”的关系。子类对象一种特殊的父类实例。
java是单继承,只能有一个直接父类,但是可以有多个间接父类。
eg: Fruit extends Plant
Apple extends Fruit
子类是父类的扩展。 当子类extends父类之后,就可以自动从父类那里“获取”如下东西(这就是为啥叫继承):
1. field。
2. 方法。
但是 子类不能获得父类的构造函数。
如果你没有显式指定extends ,默认是extends Object。 "所有类都是Object的子类"
重写父类方法
如果子类对于继承下来的父类的方法不满意,可以重写这个方法。
‘两同两小一大原则’
“两同” 方法名相同,参数列表相同
“两小” 返回值类型要小,抛出的异常要小
“一大” 权限要大
重载和重写:是两个东西。重载是一个类的多个方法,由于形参列表不同所以不同;重写是子类又写了一个和父类对应的方法。
九、 super
super有两个作用:
A super限定:
强制指定访问从父类继承得到的field、调用从父类继承得到方法。
需要在子类方法中调用父类被覆盖的实例方法(不能出现static修饰的方法)
super.Field
super.方法名
super限定的作用:
如果当前类中定义了与父类同名的field、重写了父类的方法
——如果在该类中直接访问该field、调用方法,默认是访问当前类的field、调用当前类的方法。
class BaseClass
{
public int a = 5;
}
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 sc = new SubClass();
sc.accessOwner(); // 输出7
sc.accessBase(); // 输出5
}
}
B super调用
super调用是子类调用父类的构造器。 到底调用哪个构造器,取决于传入的参数。
this调用是一个构造器调用另一个重载的构造器。 到底调用哪个构造器,取决于传入的参数。
super调用只能在构造器用,而且必须位于第一行。
this调用与super调用不能同时出现。
【规则:】 子类构造器【总】会调用父类的构造器。只调用一次!
1. 如果子类构造器中,没有使用super调用,子类构造器会自动调用父类无参数的构造器。
2. 如果子类构造器中有使用super调用,根据传入的参数显示调用父类对应的构造器。
【注意】: 子类构造器总会调用父类的构造器。
当父类没有无参数的构造器时,子类的构造器中【必须显式】地用super调用父类有参数的构造器。
否则:子类构造器会自动(隐式)调用父类无参数的构造器——必然导致找不到构造器的错误
总结: 无论你创建哪个对象,系统最先调用的总是Object类的无参数构造器。
注意:如果父类中的方法需要被外部类调用,用public修饰
不希望子类重写该方法,用final修饰符
希望父类的某个方法被子类重写,但不希望被其他类自有防问,用protected修饰
何时需要父类子类继承关系?
- 子类需要额外增加属性
- 子类需要增加自己独有的行为方式
class Creature
{
public Creature()
{
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature
{
public Animal(String name)
{
System.out.println("Animal带一个参数的构造器,"
+ "该动物的name为" + name);
}
public Animal(String name , int age)
{
// 使用this调用同一个重载的构造器
this(name);
System.out.println("Animal带两个参数的构造器,"
+ "其age为" + age);
}
}
public class Wolf extends Animal
{
public Wolf()
{
// 显式调用父类有两个参数的构造器
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args)
{
new Wolf();
}
}
十、多态
多态性
多态的定义:同一个类型的变量,调用同一个的方法(做同一件事情),呈现出多种行为,这就是多态。
对于一个引用变量来说,可以认为有两个类型:
1. 编译时类型。声明该变量时所用的类型,就是编译时类型。
对于编译器来讲,编译器只管变量的编译时类型,与运行时类型无关。
2. 运行时类型(实际类型)。该变量实际所指向的对象的类型,就是运行时类型。
相同类型的变量,调用同一个方法时呈现出多种不同的行为特征,这就是多态。
成员变量不具有多态性。
eg:Object p=new person//把子类对象直接赋给一个父类引用变量
编译时类型object ,运行时类型person
P只能调用object类的方法,而不能调用person
若需要它调用person 类的方法,则需要强制转换为运行时的类型
引用变量的强制类型转换
如果想让引用变量调用它运行时的类型的方法,则需要强制类型转换。只能是具有继承关系的两个类型之间。如果是两个没有任何关系的,无法通过编译。如果父类实例转化成子类实例,则这个对象在运行时候必须是子类实例才行,否则会有异常。用instanceof处理。
public class ConversionTest
{
public static void main(String[] args)
{
double d = 13.4;
long l = (long)d;
System.out.println(l);
int in = 5;
// 试图把一个数值类型的变量转换为boolean类型,下面代码编译出错
// 编译时候会提示: 不可转换的类型
// boolean b = (boolean)in;
Object obj = "Hello";
// obj变量的编译类型为Object,Object与String存在继承关系,可以强制类型转换
// 而且obj变量实际上类型是String类型,所以运行时也可通过
String objStr = (String)obj;
System.out.println(objStr);
// 定义一个objPri变量,编译类型为Object,实际类型为Integer
Object objPri = Integer.valueOf(5);
// objPri变量的编译时类型为Object,objPri的运行时类型为Integer,Object与Integer存在继承关系
// 可以强制类型转换,而objPri变量实际上类型是Integer类型,
// 所以下面代码运行时引发ClassCastException异常
String str = (String)objPri;
}
}
instanceof运算符
instanceof运算符前一个是引用变量,后一个是一个类型。用于判断前面的对象是不是后面的类,或者是不是他的子类,或者实现类的实例。如果是,返回true
前面操作数的编译时候的类型要么和后面的类型相同,要么和后面的有父子继承关系(不一定是子类,可以是父类),否则编译有错误。
编译想通过的话,只要有继承关系就行。想返回true,前面的不能是后 面的父类。
限制: instanceof,只能在两个类型具有继承关系才可判断,否则编译时就会报错。
public class InstanceofTest
{
public static void main(String[] args)
{
// 声明hello时使用Object类,则hello的编译类型是Object,
// Object是所有类的父类, 但hello变量的实际类型是String
Object hello = "Hello";
// String与Object类存在继承关系,可以进行instanceof运算。返回true。
System.out.println("字符串是否是Object类的实例:"
+ (hello instanceof Object));
System.out.println("字符串是否是String类的实例:"
+ (hello instanceof String)); // 返回true。
// Math与Object类存在继承关系,可以进行instanceof运算。返回false。
System.out.println("字符串是否是Math类的实例:"
+ (hello instanceof Math));
// String实现了Comparable接口,所以返回true。
System.out.println("字符串是否是Comparable接口的实例:"
+ (hello instanceof Comparable));
String a = "Hello";
// // String类与Math类没有继承关系,所以下面代码编译无法通过
// System.out.println("字符串是否是Math类的实例:"
// + (a instanceof Math));
}
}
十一、 继承和组合
使用继承的注意点
- 尽量隐藏父类的内部数据,父类的所有成员变量设置private,不让子类访问
- 不要让子类可以随便访问更改父类的方法。辅助方法设置成private;
如果父类中的方法需要被外部类调用,但是又不想让子类重写这个方法,可以加final;
如果希望子类重写该方法,但是不希望其他类自由调用,用protected - 父类的构造器里面不要有子类会修改的方法。否则容易出现空指针。
因为创建子类的对象的时候,一定会先调用父类的构造器,构造器里面调用的方法已经被子类覆盖,所以运行时用的方法是子类的方法。但是子类还没有创建,因此会有空指针的可能性。
什么时候从父类派生子类?
- 增加了新的属性
- 增加了新的方法
使用组合来实现复用(看不懂 )
组合是一种“has”的关系,譬如人有胳膊,腿。
组合和继承的开销一样。
至于怎么用的,书里面写的太少了。没有看明白
先定义private的父类的成员变量,然后在子类的构造器的形参中加入这个成员变量。然后把父类里面所有的public再写一次。(父类子类并不是这样,只是我不知道怎么能够说清楚)
十二、初始化块
使用初始化块
当创建java对象时,先调用类里定义的初始化块,
类中可以总包含5个成员:
- field
- 方法
- 构造器
- 初始化块。
- 内部类(接口|枚举)
初始化块, 它是属于类的一种成员。
[修饰符]
{
执行性的代码。
}
初始化块没有名字!不能主动调用它, 它会在合适时候时会自动执行。
修饰符 - 只能是static。有static修饰,表明该初始化块属于类, 类初始怀块(静态初始化块)
没有static修饰,表明该初始化块属于对象, 实例初始怀块(非静态初始化块)
先执行初始化块或声明实例变量时指定的初始值,再执行构造器里指定的初始值
实例初始化块(非静态初始化块)
实例初始化块:
1. 每次创建对象时,总会先执行实例初始化块、在执行对应构造器中的代码。
2. 无论你调用哪个构造器,执行构造器代码之前,总会执行实例初始化块。
类初始怀块(静态初始化块)
静态初始化总是比普通初始化块先执行,
先执行顶层父类的静态初始化块,再执行其直接父类的静态初始化块,最后再执行本身的静态初始化,
再开始执行最顶层父类的初始化块、构造器,再执行父类的初始化块、构造器,最后再执行本身的初始化和构造器
静态初始化块不能访问非静态成员
静态初始化块:
1. 它负责对类进行初始化,只在该类初始化才执行。
在一次JVM进程中,类只在第一次主动使用时加载、初始化,因此类初始化块只有一次执行机会。
public class StaticInitTest
{
// 先执行静态初始化块将a静态成员变量赋值为6
static
{
a = 6;
}
// 再将a静态成员变量赋值为9
static int a = 9;
public static void main(String[] args)
{
// 下面代码将输出9
System.out.println(StaticInitTest.a);
}
}