封装、继承、多态
封装
访问修饰权限:
private(私有), default(同一包), public(共有), protected(同一包和子孙类)
如果不写修饰符默认用default(package/friendly)修饰。
default和protected区别:protected能访问子孙类,default不能。
会被子类继承的方法,通常使用protected
继承
接口和抽象类的主要区别?
从概念上来说
抽象类是一种对事物的抽象,而接口就像是一种约定,是一种对行为的抽象;
抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
抽象类是一种模板式设计,而接口是一种行为规范,是一种辐射式设计。
模板式设计:如果B和C都使用了公共模板A,如果他们的公共部分需要改动,那么只改动A就可以了;
辐射式设计:如果B和C都实现了公共接口A,如果现在要向A中添加新的方法,那么B和C都必须进行相应改动。
区别1:
一个类只能继承一个父类。
一个类可以实现多个接口
区别2:
抽象类可以定义
public,protected,package,private、静态和非静态属性、final和非final属性
但是接口中声明的属性,只能是public、静态、final的
问题:Override(重写)和Overload(重载)的区别?Overload能改变返回值类型吗?
子类重写父类方法:子类的方法名与父类的一样,但是参数类型不一样。
重载:本类中出现的方法名一样,参数列表不同的方法。
与返回值类型无关。
思考:如果没有重写这样的机制,会发生什么?
答:一旦继承了父类,所有方法都不能修改了。另外,对象调用方法的时候,先找子类本身的方法,再找父类。(就近原则)
隐藏,就是子类覆盖父类的类方法。(重写是子类覆盖父类的对象方法 )
为什么Java语言不支持c++所有的多重继承?
多重继承有它的弊端。
1)多重继承存在二义性。比如,类C同时继承类A和类B,如果类A和类B中都有方法f,那么调用类C的的f方法时,无法确定是调用类A还是类B的方法,将会产生二义性。但是Java语言却可以通过实现多个接口的方式间接地支持多重继承,由于接口只有方法体,没有方法实现,假设类C实现了接口A和接口B,即使AB都有f方法,但接口只有定义没有实现,在C中才有一个方法的实现,也就不存在二义性了。
2)多重继承会使得类型转换,构造方法的调用顺序变得非常复杂。
多态
操作符的多态
加号+可以作为算数运算,也可以作为字符串连接。不同情境下,具备不同的作用
如果+号两侧都是整型,那么+代表数字相加
如果+号两侧,任意一个是字符串,那么+代表字符串连接
类的多态 比如父类的引用指向子类的对象、重写。
下面程序的运行结果是?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class Base
{
int num = 1;
public Base(){
this.print();
num=2;
}
public void print(){
System.out.println("Base.num="+num);
}
}
class Sub extends Base{
int num= 3;
public Sub(){
this.print();
num=4;
}
public void print(){
System.out.println("Sub.num="+num);
}
}
public class Test1 {
public static void main(String[] args)
{
Base b = new Sub();
System.out.println(b.num);
}
}
在执行语句 Base b= new Sub时,会首先调用父类的构造方法。根据多态的特性,此时实例化的是sub类的对象,因此,base构造方法会调用Sub类的print()方法。由于此时Sub类中的初始化代码 Int num=3还没有执行,num的默认值为0,输出为 Sub num=0。 下一条语句父类num初始化为2。
然后会调用子类的构造方法,根据初始化的顺序可知在调用子类构造方法时,非静态的变量会先执行初始化动作,所以,此时子类Sub的mum值为3,因此,调用 print方法会输出 Sub num=3。
接着输出b.num,由于b的类型为Base,而属性没有多态的概念因此,此时会输出父类中的mm值:2
程序的运行结果如下:
Sub.num=0
Sub.num=3
2
题目总结:1.当父类的引用指向子类的对象时,会先初始化父类。
2.如果子类重写某方法,不管父类子类都是调用子类方法。
3.属性没有多态的概念。
其他
java初始化原则
对象属性初始化方法有3种
- 声明该属性的时候初始化
- 构造方法中初始化
- 初始化块
如果同时初始化同一变量,则优先级是:构造方法中初始化>初始化块> 声明该属性的时候初始化
静态成员变量>成员变量>构造方法。
1静态变量优先与非静态变量
2父类优先于子类
3按照成员变量定义的顺序。
父子类的初始化执行顺序如下:
父类静态变量,父类静态代码块
子类静态变量,子类静态代码块
父类非静态变量,父类非静态代码块,父类构造方法
子类非静态变量,子类非静态代码块和子类构造函数。
this关键字
this关键字代表自身实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//参数名和属性名一样
//在方法体中,只能访问到参数name
public void setName1(String name){
name = name;
}
//为了避免setName1中的问题,参数名不得不使用其他变量名
public void setName2(String heroName){
name = heroName;
}
//通过this访问属性
public void setName3(String name){
//name代表的是参数name
//this.name代表的是属性name
this.name = name;
}
实参与行参的问题
猜一猜程序运行结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Test {
public void change(int j, StringBuffer ss1) {
j = 100;
ss1.append("world");
}
public static void main(String[] args) {
int i = 1;
StringBuffer s1 = new StringBuffer("hello ");
Test t = new Test();
t.change(i, s1);
System.out.println(i);//1处
System.out.println(s1);//2处
}
}
由于i是基本类型,因此参数是按值传递。会创建一个i的副本。把这个副本作为参数赋值给j。既然j是i的副本,那么对副本的任何修改都不会对i有影响。因此1处输出1。
由于StringBuffer是一个类,因此是按引用传递。当ss1修改的时候。由于实参s1和形参ss1指向的是同一块储存空间,因此ss1修改了值之后,s1指向的字符串也被修改了。因此2处输出hello world。
那么,下面程序运行结果又是什么1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public void change(StringBuffer ss1) {
ss1 = new StringBuffer("world");
}
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer("hello ");
Test t = new Test();
t.change(s1);
System.out.println(s1);
}}
由于StringBuffer是一个类,因此是按引用传递。但是这里的change方法不是对”hello “进行修改,而是使形参ss1的指向另一个字符串 “world”。而对形参ss1的改变对实参s1没有影响,实参s1仍然指向 “hello “。
引用就是指针吗?
不是,二者不能等同。虽然java引用在底层是通过指针实现的,但指针可以执行比较运算和整数的加减运算,而引用却不行。
关键字 final
修饰变量时,用以定义常量;
修饰方法时,方法不能被重写(Override);
修饰类时,类不能被继承。
变量、数组、循环
变量
变量的范围
1.变量声明在类下,叫做字段或者属性或者成员变量
2.变量声明在一个方法上的,就叫做参数或者局部变量
数据类型
八种基本类型(二进制位数):
整型 4种: byte(8位)、short(16位)、int(32位)、long(64位)
浮点型 2种: float(32位)、double (64位)
字符型 1种: char(16位)
布尔型 1种:boolean(1位)
现在问题来了
问:int和integer的区别?
答:1)int默认值为0。而integer默认值为null。由此可见,证int无法区分未赋值与赋值为0的情况,而integer却可以区分这两种情况。
2)int是是值传递。而integer是引用传递
3)int只能用来运算,而integer提供了很多有用的方法
4)当需要往容器(例如List)里存放整数时,无法直接存放int,因为List里面放的都是对象,所以,在这种情况下只能使用 Integer
问:char型变量中能不能存贮一个中文汉字?为什么?
答:char是16位的,占两个字节
汉字通常使用GBK或者UNICODE编码,也是使用两个字节,可以正常存放汉字。如果是utf-8编码,一个中文占三个字节,编译不会报错。但运行会报error“未结束的字符文字”
问:请解释这三条语句的输出1
2
3 System.out.println((byte)127);//127
System.out.println((byte)128);//-128
System.out.println((byte)129);//-127
答:byte的取值是{-128,127},如果把128强制转换成byte已结超出了byte范围,此时会溢出,相当于最小的负数-128.而129强转后就是-127
问:请解释这条语句的输出1
System.out.println(Math.min(Double.MIN_VALUE,0.0));//输出0.0
答:对于Double来说MIN_VALUE并不是取值范围的最小数,而是正数范围的最小数,也就是最接近于0的正数。最接近于0的正数和0比起来,当然是0小。
问:在java里调用什么方法能把二进制数转化为十进制?
答:1
System.out.println(Integer.valueOf("11101",2));
类型转换
强制转换
1 | int i1 = 10; |
自动装箱和拆箱
把基本数据类型和对应的包装类之间转换。比如int和Integer。1
2Integer i = 100; //自动装箱,编译器执行Integer.valueOf(100)
int j = i; //自动拆箱,编译器执行i.intValue()
==与equals()
==比较的是两个对象的引用是否相同,或者是比较原始数据类型是否相等;
equals()比较的是两个对象的内容是否相同。
关于变量的题目
题目一:解释为何行3编译错误,而行4编译正确1
2
3
4
5
6 byte b1 = 3;
byte b2 = 4;
byte b3 = b1 + b2; //编译错误
byte b4 = 3 + 4; //编译正确
//(1)变量相加,首先首先进行类型提升,之后再进行计算,计算后将结果赋值;
//(2)常量相加,首先进行计算,之后判断是否在接受类型的范围,在则赋值。
题目二:判断下列代码是否有误,并指出错误1
2
3
4
5
6 short s = 1;
s = s + 1; //错误,s在参加运算时会自动提示类型为int。int类型值无法直接赋值于short类型
short z = 1;
z += 1; //正确,扩展赋值运算符包含强制类型转换。等价于 z = (short)(z + 1);
//还有,-128~127的Integer值可以从缓存中取得。其他情况要重新创建
题目三,int i = 1;i+=++i;的运算结果?
i+=++i,其中先算++i,得到2
由于++i并未进行赋值,所以i还是1
1+=2结果为3
数组
一维数组的3种创建方式1
2
3
4int[] arr1 = {1,2,3,4}; //正确
int[] arr2 = new int[4]; //正确
int[] arr3 = new int[]{1,2,3,4}; //正确
int[] arr4 = new int[4]{1,2,3,4}; //错误,编译不通过
二维数组的3种声明方式1
2
3int arr1[][];
int [][]arr2;
int []arr3[];
与C/C++不同的是,java的二维数组允许第二维的长度可以不同。
循环
for循环
写出下面程序运行结果1
2
3
4
5
6
7
8
9
10
11
12public class Test {
static boolean p(char c) {
System.out.print(c);
return true;
}
public static void main(String[] args) {
int i=0;
for (p('a'); p('b')&&i<2; p('c')) {//for(表达式1;表达式2;表达式3){循环体]
p('d');
i++; } }}
因为,1.初始化只会执行一次2.先执行循环体后执行for循环的表达式3
所以答案是:abdcbdcb
switch
1 | switch(day){ |
使用switch特别注意,必须在case语句后加break
枚举
1 | public enum Season {//是枚举enum不是类class |
如何跳出多重循环?
在外部循环的前一行,加上自定义标签,比如 out:
在break的时候使用该标签。break out;
内部类
非静态内部类
可以被看作外部类的一个成员(与类的属性和方法类似)
1可以自由的引用外部类的属性和方法。2外部类被实例化之后,内部类才能被实例化。3不能有静态成员。
1 | public class Hero {//英雄类 |
静态内部类。
1不能访问外部类普通成员,只能访问外部内中静态成员和静态方法。
2可以不依赖于外部类实例化而实例化。
3不可以与外部类同名。
局部内部类
是指定义在一个代码块内的类,不可以被修饰符修饰。
匿名内部类
是一种没有类名的内部类,不使用关键字class、extends、implement,没有构造方法,必须继承类或其他接口。一般用于gui编程中事件处理。
1 | public abstract class Hero{ |
字符串
格式化
%s 表示字符串
%d 表示数字
%n 表示换行1
2
3
4
5
6
7String name ="盖伦";
int kill = 8;
String title="超神";
String sentenceFormat ="%s 在进行了连续 %d 次击杀后,获得了 %s 的称号%n";
//使用printf格式化输出
System.out.printf(sentenceFormat,name,kill,title);//第一个是原字符串
StringBuffer追加 删除 插入 反转
1 | String str1 = "let there "; |
字符串的转化
数字与字符串
- 数字转字符串
方法1: 使用String类的静态方法valueOf
String str = String.valueOf(i);
方法2: 先把基本类型装箱为对象,然后调用对象的toString
Integer it = i;
String str2 = it.toString();
- 字符串转数字
String str = “999”;
int i= Integer.parseInt(str);
字符串与字符串数组
- 字符数组 转 字符串
char[] data={‘a’,’b’,’c’};
String s=new String(data);
字符数组转换成字符串1
2char[] data={'a','b','c'};
String s=new String(data);
s1.charAt(0)=c1;是不行的
s1.charAt(0);是可以的
字符串是否相等的提问
问:1和2处分别输出什么?1
2
3
4
5
6
7
8 String str1 = "the light";
String str2 = new String(str1);
System.out.println( str1 == str2);//1
String str4 = new String("the light");//2
String str3 = "the light";
String str4 = "the light";
System.out.println( str4 == str3);//3
答:1输出false。因为new String会为str2开辟一个新的区域.
2输出true。因为str3创建了一个新的字符串”the light”,在str4编译器发现已经存在现成的”the light”,那么就直接拿来使用,而没有进行重复创建
时间
格式化时间
时间(注意月和小时是大写的喔)
y 代表年
M 代表月
d 代表日
H 代表24进制的小时
h 代表12进制的小时
m 代表分钟
s 代表秒
S 代表毫秒1
2
3
4
5Date d = new Date();
System.out.println(d);//输出当前时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日,HH时mm分ss秒");//设置样式
String s = sdf.format(d);//把d这个时间实例格式化,并且赋值给字符串s
System.out.println(s);//输出的字符串s就是格式化好的时间啦
日历(可以做“查看明年的今天是几号”之类的事)
1 | Calendar c = Calendar.getInstance();//Calendar用单例模式创造实例 |
一些基础但不重要的知识
- 变量名起名规则
- 字母 数字 $ _ 组成
- 变量第一个字符,不能使用数字。
- 不可以使用关键字。
为什么不重要?
起名的时候选择有意义描述性的词比如“toString””getFlow”,不要用关键字。起名规则无需死记硬背。