这一篇文章我将和大家一起回顾一下OOP面向对象的相关知识,前几天和女朋友分手之后就一直想好好整理一下这部分的内容(开个玩笑哈哈)内容大概共分为7块供大家一起讨论学习,如果有不对的地方欢迎大家指正、讨论。(建议收藏哦,确实比较全面2333)
java语言基础:
开发环境
-
搭建java开发环境
- 下载并安装JDK
- 配置环境变量
- JAVA_HOME:jdk的安装路径
- path:jdk–>bin的路径
- classpath:.
-
JDK,JRE,JVM
-
java程序的运行机制:先编译后运行
变量:
- 变量的声明: 数据类型 变量名称;
- 变量的初始化:第一次赋值
- 变量的访问:获取变量中的值或给变量赋值
注意点:
- 变量的命名:4点
- 是由字母,数字,_,$组成,数字不能开头
- java中严格区分大小写,不能使用java关键字/保留字
- 见名知意,驼峰命名法
- java中不建议使用中文
基本数据类型:8种
- byte 1字节
- short 2字节
- int 4字节
- long 8字节
- float 4字节
- double 8字节
- boolean 1字节
- char 2字节
整数类型:
-
int,long:
-
赋值时注意数据溢出,java中给出一个整数默认是int类型,如果要定义long类型的变量,赋值时若该值超过了int的范围,需要+L/l
-
byte,short同样需要注意数据溢出
-
注意点:如果是整数直接量之间的运算并赋值,编译器可以进行优化,将结果优化成想要的数据类型
byte b = 1+2;
-
-
浮点类型:
- float,double精度问题
- float类型定义变量,赋值时值后需要+F/f,JAVA中给出小数,默认是double类型
- double不能用于做精确运算,如要做精确运算,使用BigDicaml类来操作
-
char:占用2个字节,保存在内存中的值是该字符对应的unicode码值(整数)
- 赋值: ‘’ 整数 ‘\u4e00’
-
类型转换:
- 自动转换: 小转大
- 强制转换: 大转小
注意点:char类型可以自动转换为int类型,但是不能和byte,short进行自动转换,若需要,可以进行强制转换
-
不同数据类型间的运算:
- 不同数据类型间进行运算,最后的结果的数据类型一定是和运算中最高的数据类型保持一致的
- byte,short,char类型间进行运算,都是先提升为了int类型,再运算的,所以最后的结果一定int类型的
-
注意点:编译器优化问题
byte b = 1+2; //编译正确 byte b = b1+3; //编译错误
-
java中的数据类型总体可以分为2种:
基本数据类型
引用类型
运算符:
- 算术运算符
- ++ –
- 若i++ ;是先复制,后运算;若++i:是先运算,后赋值
- ++ –
- 关系运算符
- 逻辑运算符:
- && || !
- 注意短路问题
- 赋值运算符:
-
- += -= *= /= %=
-
注意赋值运算符自带强转功能
byte b= 3; b += 3; //正确 b = b+3; //编译错误的
-
- 字符串连接运算符 +
- 三目运算符:
- 数据类型 变量 = 表达式(boolean)?表达式1:表达式2;
分支结构:2种
- if…else:用于做范围判断分支
- switch…case:用于做精确判断分支
- 参数类型只能是int,String,enum
- 每条分支的最后加+break;若不加,将顺序往后执行,直到遇到break为止
循环结构:3种
-
while循环
-
do…while循环
-
for循环
-
死循环:3种循环都能实现,有些场景是需要死循环来实现的
-
break和continue的区别:
- break:跳出当前循环
- continue:跳出本次循环,继续下次循环
- return关键字作用于方法中,可以单独使用,若单独使用,执行到此关键字,跳出方法
-
嵌套循环:
- 考算法
基本类型数组:
-
数组的声明:2种
- int[] ary; 建议使用
- int ary[];
-
数组的初始化:3种
- int[] ary = new int[length];
- int[] ary = {1,2,3};
- 此种方式初始化必须在声明的同时进行
- int[] ary = new int[]{1,2,3};
-
数组的访问:
- 通过下标访问数组元素—注意下标越界问题
- 遍历数组 – for循环实现
-
数组的复制:
-
System.arraycopy(Object src,int srcPos,Object dest,int destPos,int length);void
-
Arrays.copyOf(Object src,int newLength):T[]
- 常用于做数组的扩容/缩容
两种方式相比较而言,第一种更灵活,第二种经常用于做扩容/缩容操作
-
-
数组的排序:
Arrays.sort(T[])Arrays类中常用方法: Arrays.copyOf(Object src,int newLength) Arrays.sort(T[]) Arrays.toString(T[])
排序算法:
-
冒泡排序 – 必须掌握的
考试内容:使用冒泡对数组进行排序 – 代码实现要求必须会
方法:5部分
- 修饰符 返回值类型 方法名(参数列表){方法体}
- 返回值类型:
- 方法若有返回值类型,可以是基本数据类型,也可以是引用类型;方法体中一定有return + 表达式/变量/val值
- 没有返回值:void
- 返回值类型:
不论方法有没有返回值类,return都可以单独使用(作用:跳出当前方法)
-
方法名:见名知意,驼峰命名
-
参数列表:
- 参数类型:形参,实参
- 可变参数:int…is – 4点注意点
- 参数列表中若有可变参数,一定是位于最后
- 参数列表中最多只能有一个可变参数
- 可变参数底层是数组,访问方式和数组相同
- 可变参数底层是数组,所以可以将一个数组作为参数传给可变参数
- 可变参数:int…is – 4点注意点
- 参数类型:形参,实参
注意点:java中的参数传递都是值传递(面试题)
猜字符游戏
-
得分:总分100,猜错一次扣10分
-
升级版本 – 作业
升级内容:- 生成的字符数组长度由用户决定
- 添加对输入字符串的长度判断
- 用户决定生成的字符数组的长度为n,接受用户输入的字符串,添加对此的判断:
- 若用户输入的字符串的长度!=n,给出提示"输入长度有误,请重新输入!",此时算猜错一次.
- 用户决定生成的字符数组的长度为n,接受用户输入的字符串,添加对此的判断:
- 添加对输入次数的判断:
- 总分100分,最多能答错10次,若现在已经答错10次,再次输入,给出提示"机会已用完!",程序结束
-
猜字符游戏的思路:
- 进入游戏,给出提示"欢迎进入猜字符游戏…"
- 由用户输入目标字符数组的长度 - n
- 生成目标字符数组 – generate(n)
-
public char[] generate(int n): char[] letters = {26个英文字母}; //flag数组中的每个元素默认表示letters中的每个字符没被取,若某个字符被取走了, 此时对应下标对位的flag数组中的元素置为true boolean[] flag = new boolean[letters.length]; char[] standard = new char[n]; //为standard数组生成n个字符 for(int i=0;i<standard.length;i++){ int index; do{ index = (int)(Math.random()*26); }while(flag[index]); //若为true,表示字符被取,应重新生成随机数字 standard[i] = letters[index]; flag[index] = true; } return standard;
- 接收用户输入的字符串
- 判断是否是"EXIT",若是:退出程序 return
- 如果不是"EXIT",判断输入的字符串长度是否正确:若不正确,提示"长度错误,请重新输入"
- 此时算做错误1次,要扣分的
- 若正确:此时进行内容判断
-
String -->char[]
- String类中的方法:toCharArray():char[]
-
check(char[] input,standard)
public int[] check(char[] input,char[] standard): int[] result = new int[2]; //result[0]:统计答对的字符个数(不统计位置) //result[1]:统计答对的位置正确的字符个数 for(int i=0;i<standard.length.i++){ for(int j=0;j<input.length;j++){ if(standard[i]==input[j]){ result[0]++; if(i==j){ result[1]++; } break; } } }
-
对check结果进行分析:
- 若result[1]==n;全对,给出得分,程序结束
- 否则,给出提示,猜对几个字符,继续输入(算答错一次,扣10分)
用户继续输入之前,进行机会是否用完判断,
-
OOP:Object oriented programming
-
java是面向对象的语言
-
面向过程:代码编写更接近计算机的执行方式
-
面向对象:代码编写接近人的思维方式
类和对象
-
类:是一类事物的模板
eg: 人是一个种群,不是对象,但是张三是一个对象,李四也是一个对象 class Person{ String name; int age; double height; double weight; public void walk(){..} public void eat(){....} }
类的成员:
属性
方法 -
对象:经由类这个模板创建出来的一个具体的实例
new Person() - 张三
new Person() - 李四- 对象的创建:new 类名();
- 对象的使用: 通过引用来访问对象内部的数据和方法
- Person per = new Person();
- per.属性 = val;
- String name = per.属性;
- per.方法()
注意点:
-
类中的非static的成员变量和方法都是属于对象的,调用时必须通过对象来调用
eg:Scanner sc = new Scanner(System.in); String name = sc.next(); 问题: class T{ main: 调用test() T t = new T(); t.test(); void test(){...} }
-
java程序运行后所有的数据都要保存到内存中,而创建的对象保存在内存中的堆中;
- JVM内存分成三块区域:
-
栈:保存局部变量
-
堆:保存对象
Person per = new Person();
-
- JVM内存分成三块区域:
-
非static的成员变量是属于对象的,每新创建一个对象,这些成员变量都会进行一次默认初始化
-
数组是对象
面试题:
-
下列说法错误的是( BCD)
A. 数组是一种对象 B. 数组属于一种原生类 C. int number=[]={31,23,33,43,35,63} D. 数组的大小可以任意改变 原生类:java中为8种基本数据类型提供的包装类
-
关于下列代码说法正确的是:A
public class A { public int counter = 0; public static int getInstanceCount() { return counter; //编译错误 } public static void main(String[] args) { A a1 = new A(); a1.counter++; A a2 = new A(); a2.counter--; A a3 = new A(); a3.counter+=2; System.out.println(A.getInstanceCount()); } } A.该类编译失败 B.输出:1 C.输出:3 D.输出:0
-
下面程序运行的结果是 (C)
Public class A { int a = 0 int c = 0 do{ --c }while(a>0){ Shystem.out.println(c) } } A 0 B -1 C 编译错误 D 无法计算 考点:do..while循环是写在方法中的
-
阅读以下代码,程序输出的结果是什么
public class Test{ public static void main(String[] args){ int a = 4; char[] chs = {'a','b','c'}; Test test = new Test(); test.change(a,chs); System.out.println(a); System.out.println(Arrays.toString(chs)); } public void change(int a,char[] chs){ a = 10; chs[0] = 'g'; } } 4 gbc 考点: 1.内存管理 2.java中的参数传递都是值传递
-
作业:猜字符游戏的升级版 – 自己做
/** * 猜字符游戏升级版 * 升级点: * 1.生成的目标字符数组长度由用户决定 * 2.对用户输入的字符串长度做出判断 * 3.对用户猜错的次数做限制,共100分,最多能猜错10次 * @author Admin * */ public class GuessLetterGameUpper { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int score=0,count=0; //count表示猜错的次数 System.out.println("欢迎进入猜字符游戏:"); System.out.println("请输入目标字符的长度:"); int n = scanner.nextInt(); char[] chs = generate(n); System.out.println(Arrays.toString(chs)); while(true) { //是否有机会输入 if(count>9) { System.out.println("机会已用完,正确答案是:"+Arrays.toString(chs)+",请重新开始程序!"); break; } System.out.println("请输入猜测的字符串:"); String line = scanner.next().toUpperCase(); //判断是否为exit,长度是否为5 if("EXIT".equals(line)) { System.out.println("退出程序,欢迎下次光临!"); //结束程序 break; } if(line.length()!=n) { System.out.println("输入长度有误,请重新输入!"); count++; continue; } char[] input = line.toCharArray(); int[] result = check(chs, input); if(result[1]==n) { //表示猜对了 score = 100 - 10*count; System.out.println("恭喜你,猜对了,得分是:"+score); break; }else { System.out.println("猜错了,其中猜对的字符个数是:" +result[0]+",其中,位置正确的有:"+result[1]+"个"); count++; continue; } } scanner.close(); } //生成标准字符数组 public static char[] generate(int n) { char[] chs = {'A','B','C','D','E','F','G', 'H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z'}; char[] dest = new char[n]; //char \u0000 boolean[] flag = new boolean[chs.length]; //生成5个随机字符 for(int i=0;i<dest.length;i++) { //随机生成下标 int index; do { index = (int)(Math.random()*chs.length); }while(flag[index]==true); dest[i] = chs[index]; flag[index] = true; } return dest; } //校验用户输入的值 public static int[] check(char[] standard,char[] input) { int[] result = new int[2]; //第一个元素表示猜对的字符个数,第二个元素表示位置正确的个数 for(int i=0;i<standard.length;i++) { for(int j=0;j<input.length;j++) { if(standard[i]==input[j]) { //字符猜对了 result[0]++; if(i==j) { result[1]++; } break; } } } return result; } }
<2>
方法重载:overload
- 什么是方法重载?
-
在同一类中,存在多个方法名相同,参数列表不同的方法
-
问题:参数列表不同可以有哪些不同
- 可以是数量不同
- 可以是数量相同,但是参数类型不同
- 可以是数量相同,类型也相同,但是顺序不同
-
问题:方法重载和方法的返回值类型,以及修饰符有关吗?
-
无关,方法的重载只和方法签名有关
-
方法签名:方法名+参数列表
eg:以下几个方法是否构造重载? class Test{ public String demo(){...} public void demo(){....} public int demo(int a){...} } 1和2 --不构成 1和3 --构成 2和3 --构成
-
-
构造器–重点(考点)
语法
修饰符 类名(){}
参数列表可以有参数,也可以没有参数
构造器的分类
-
无参构造器
-
有参构造器
常见用法: class T{ int age; String name; public T(){} //无参 public T(int age,String name){ //有参 - 用于给成员变量赋值 this.age = age; this.name = name; } }
-
注意点:
-
一个类中最多只能有一个无参构造方法,可以没有
- 没有的场景:人为生成有参构造方法,而没有生成无参构造方法
- 若一个类中没有人为生成构造方法,java会默认提供一个无参构造器
-
一个类中有参构造方法可以有多个
- 通常用于做成员变量的初始化
-
类中的无参构造器和有参构造器构成了方法的重载
-
构造器的作用:初始化
问题:
class T{
int a = 3;
T(){ //也完成了成员变量的初始化
}
public void init(){
//进行初始化一些数据
}
}
断点:Debug:
- 用于完成项目中错误的排查
强调点:
- 无参构造器是用于初始化,可以将一些初始化的内容写到构造器中
- 若成员变量位置进行了人为初始化,那么这个初始化是在无参构造器中完成的。
- 创建对象和构造方法的执行顺序:先创建对象,对象创建之后才执行构造方法完成初始化
this
-
出现在了有参构造方法中
-
this是一个引用,指向了调用所在方法的对象
- 通过对象调用某个方法时,会隐式的传递一个对象(调用此方法的对象)过去
-
this作用于方法体中的,在没有歧义的情况下,this可以省略
class T{ int a; public void test(){ this.demo(); //this可以省略 this.a = 3; //this可以省略 } public void demo(){ } } 不可省略的场景: class T{ int age; String name; T(int age,String name){ this.age = age; this.name = name; } }
-
this不可以用于static方法体中
-
static方法是属于对象的吗,调用时是需要通过对象调用吗? 不是属于对象的,调用通过类名调用
-
若现在要调用一个static方法,此时不需要通过对象调用,此时隐式传递的对象存在吗? 不存在
-
所以,static方法体中没有this
-
-
this关键字的使用:
- this.实例变量
- this.实例方法
- this() – 调用无参构造方法
- this(参数) – 调用有参构造方法
==和空指针问题
-
== 两边是引用类型,此时的作用是比较2个引用是否指向同一对象
class Point{ int x; int y; Point(){} Point(int x,int y){ this.x = x; this.y = y; } main: Point p1 = new Point(20,20); Point p2 = new Point(20,20); p1==p2; ? false }
-
空指针问题:
- 引用.实例变量/实例方法;
- 若此时引用为null,此时会报NullPointerException()
面试题:
-
现有代码如下,以下哪些选项是正确的(ace)
class Test{ public String methodA(int a,String str){ return "方法A"; } //插入代码处 } A. public void methodA(byte b,String str){} B. public String methodA(int m,String k){return ""} //参数列表不同只看类型和顺序 C. public String methodA(int a){return ""} D. public int methodA(int a,String str){return 1} E. public String methodA(String str,int x){return ""} test(){ methodA(5,""); --调用第一个方法 //因为5默认是int类型 byte b = 3; methodA(b,"") //调用参数是byte类型的方法 } 考点:方法重载:只看方法名(相同)和参数列表(不同的)
-
(单选)下列代码的输出结果是:©
//包装类:8种基本数据类型,Java中为8种基本数据类型都提供了对应的包装类 long -- Long:完成对long类型的一些操作(方法) int -- Integer short - Short 注意:可以将基本数据类型变量直接赋值给对应的引用类型 long l1 = 34L; Long l = l1; short s = 3; Short s1 = s; public class Yikes { public static void go(Long n) { System.out.println("Long"); } public static void go(Short n) { System.out.println("Short"); } public static void go(int n) { System.out.println("int"); } public static void main(String[] args) { short y = 6; long z = 7; go(y); //此处该调用哪个方法? short可以自动提升为int,也可以自动转换为Short //此时采用就近原则(哪种转换更快/更便捷,就哪个近) y -->int :更便捷的 y -->Short:稍微复杂/慢一点 go(z); //Long } } A.Long Long B.Short Long C.int Long D.int int
-
下列说法正确的有(C)
A、class中的constructor 不可省略 //若一个类种没有有参,此时无参可以省略 B、constructor必须与class同名,但方法不能与class同名 C、constructor是在对象创建后进行初始化的 D、一个class只能定义一个constructor
-
以下说法正确的是(CD)
A. static方法中可以通过this调用实例方法 //static方法中没有this B. static方法中可以直接访问该类中的实例变量 //不可以 C. 实例方法中可以使用this访问本类中的实例变量和实例方法 D. 实例方法中可以调用本类中的static方法,也可以访问本类中的static变量 //可以的,但是不建议这么做的 class T{ static int a = 4; static void test(){} void demo(){ 输出a; test(); } } 总结:static方法不能直接访问非static成员,但是非static方法可以直接访问static的,但是不建议
-
给出下面的代码段,在代码说明//assignment 处写下如下哪几个代码是正确的?(cd)
public class Base { int w, x, y, z; public Base(int a, int b) { x=a; y=b; } public Base(int a, int b, int c, int d) { //assignment } } A Base(a, b); //语法错误 Java中不允许在构造方法中直接通过构造方法名调用构造方法 B x=a, y=b; //应该用;隔开 C x=a; y=b; D this(a,b); Java中若在构造方法/普通方法中通过方法名调用方法,调用到的一定是普通方法
<3>
引用类型数组
-
数组中的元素类型若是引用类型,那么这个数组就是引用类型数组
-
数组是对象,数组可以作为引用类型
-
数组中的元素可以是数组 - 多维数组
- 二维数组 - 数组的数组
- 在内存中的保存形式
问题:数组中的每个元素指向的数组长度可以是不同的.此时需要注意:
数组的初始化就不可以使用 以下方式:
int[][] ary = new int[3][2];此时应该这样初始化: int[][] ary = {{2,3},{1,2,3},{1}}
- 二维数组的遍历:
for(int i=0;i<ary.length;i++){
//ary[i] //指向第二维的数组
for(int j=0;j<ary[i].length;j++){
int a = ary[i][j];
System.out.println(ary[i][j]);
}
}
继承,super
-
继承:
- 实现方式:extends 子类 extends 父类
- 继承的特点:2点
- 传递性
- 单继承:
- java中类的继承是单继承的
- 即一个子类只能有一个父类,反之,一个父类是可以由多个子类的
- java中类的继承是单继承的
- 继承中构造方法的调用问题
-
创建子类对象,会先调用父类中的构造方法,再调用子类中的构造方法
class Super{ public Super(){ System.out.println("父类中的构造方法"); } } class Sub extends Super{ public Sub(){ super(); System.out.println("子类中的构造方法") } } class Test{ main: new Sub(); }
-
- 继承中不继承的成员:
- 构造方法
- 私有成员 – private
- static成员
super:
- 是一个引用,指向父类的对象
- 用法:
-
super.成员变量
-
super.实例方法
-
super() :调用父类中的无参构造方法
-
注意:在子类构造方法中默认会调用父类的无参构造方法,但是我们可以人为调用父类中的有参构造方法
案例: class SuperCls{ int age; public SuperCls(int age){ this.age = age; //执行其他代码 } } class SubCls extends SuperCls{ public SubCls(){ super(3); //要求先初始化的内容是父类中有参构造方法中初始化的内容 //其他的初始化 } }
-
向上造型
- 通过父类的引用指向子类对象
-
FlyingObject obj = new AirPlane();
class Person{ String name; int age; } class Student extends Person{ int id; } Person per = new Student(); per.父类中的成员
-
注意:通过引用只能访问到父类中的成员,访问不到子类中的
-
向上造型的缺点:
- 访问不到子类中特有的成员 – 向下造型
-
面试题
-
已知如下代码:
1: class Example{ 2: String str; 3: public Example(){ 4: str= "example"; 5: } 6: public Example(String s){ 7: str=s; 8: } 9:} 10: class Demo extends Example{ 11: } 12: public class Test{ 13: public void f () { 14: Example ex = new Example("Good"); 15: Demo d = new Demo("Good"); 16: } } 哪句语句会导致错误?( E) A、line 3 B、line 6 C、line 10 D、line 14 E、line 15
-
以下哪些是正确的(A)
class Person{} class Student extends Person{} class T{ public static void main(String[] args){ T t = new T(); //插入代码处 } public void demo(Person per){} } 考点:向上造型 A. t.demo(new Student()); B. t.demo(3); C. t.demo("hello"); D. t.demo();
-
以下代码是否编译正确?
public class Test{ } public class Sub extends Test{ public Sub(){ System.out.println("子类的构造方法"); super(); //编译错误,super()一定是位于构造方法第一行的 System.out.println("执行结束"); } }
-
以下代码是否编译正确?
class Super{ int age; public Super(int age){ this.age = age; } } class Sub extends Super{ String name; public Sub(String name){ super(); //编译错误,父类中此时没有无参构造方法 this.name = name; } }
-
以下代码是否编译正确?
class Super{ int a; int b; public Super(int a,int b){ this.a = a; this.b = b; } } class Sub extends Super{ int a,b,c,d; public Sub(int a,int b,int c,int d){ this(a,b); //编译错误,这样写表示要调用本类中的构造方法 this.c = c; this.d = d; } }
-
以下代码的输出结果是什么?
class T{ main: int a = 10; int[][] ary = {{1,2},{4,7,8}}; T t = new T(); t.change(a,ary); 输出a ? 10 输出ary ? {{1,5},{4,7,8}} void change(int a,int[][] ary){ a = 4; ary[0][1] = 5; } }
-
打印杨辉三角形 – 算法题
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 ....
要求:用户输入行数,并打印出杨辉三角形 – 用到了循环嵌套,二维数组
public static void main(String[] args) { System.out.println("请输入行数:"); Scanner scanner = new Scanner(System.in); int row = scanner.nextInt(); int[][] ary = new int[row][row]; //打印 for(int i=0;i<row;i++) { //表示行数 for(int j=0;j<=i;j++) { //表示列数 if(j==0 || j==i) { ary[i][j] = 1; }else { ary[i][j] = ary[i-1][j]+ary[i-1][j-1]; } System.out.print(ary[i][j]+"\t"); } System.out.println(); } scanner.close(); }
<4>
方法重写:override
- 概念:在具有继承关系的父子类中,子类对从父类中继承的某个方法的方法体进行重新实现
- 规则:2同2小1大 —面试题笔试题中的选择题
-
方法名相同,参数列表相同
-
方法返回值类型 子类<=父类
-
父类 void 子类 void
class Super{
public void test(){…}
}
class Sub extends Super{
public void test(){…}
} -
父类 基本类型 子类 必须相同
- eg:若父类中某方法返回值类型为long,子类对其重写,返回值类型为int,是错误的
-
父类 引用类型 子类 是其子类类型,也可以相同的
前提:class Student extends Person class Super{ public Person test(){....} } class Sub extends Super{ public Student test(){....} }
-
-
如果父类中某个方法抛出异常,子类重写此方法,抛出的异常<=父类的
-
子类重写的方法的访问控制修饰符>=父类中访问控制修饰符
class Super{ void test(){....} //默认的 } class Sub extends Super(){ private void test(){....} //此处为private是错误的,若为其他访问控制修饰符,都正确 }
-
- 方法重载(overload)和方法重写(override)的区别 — 面试题
- 分别描述二者
- 方法重载是指在同一个类中,存在多个方法名相同,参数列表不同的方法
- 方法重写是指在具有继承关系的父子类中,子类对继承自父类的某个方法的方法体进行重新实现
- 重载遵循编译期绑定原则,重写遵循运行期绑定原则的
- 分别描述二者
访问控制修饰符:4种
public:修饰的成员可以被所有类访问的
protected:修饰的成员可以被本类访问,可以给同包下的类访问,还可以被不同包下的派生类访问
默认的:修饰的成员可以被本类访问,可以被同包下的其他类访问
private:修饰的成员只能被本类访问
static,final
-
static和final都是修饰符
-
final:是一个修饰符:最终的,不变的
- 变量:一旦初始化,值不可以改变
- 可以是成员变量,也可以是局部变量
- 方法:不可被重写
- 类:不可以被继承
eg: final Person per = new Person();
per是引用,是一个变量,不是对象 - 变量:一旦初始化,值不可以改变
-
static:是一个修饰符:
static修饰的成员都是属于类的,都是保存在方法区的- 成员变量:
- 属于类的,通过类名调用,保存在方法区,在类加载时期进行初始化,只加载一次
- 方法:
- 属于类的,通过类名调用,保存在方法区
- 注意点:static方法体中不能使用this关键字
- 原因:调用非static方法时,会隐式的传递一个参数(调用方法的对象),用this接受的, 而调用static方法,通过类名调用的,不是通过对象调用的,所以static方法体中不存在this
- 代码块:
- 属于类的,保存在方法区,在类加载时期执行,且只执行一次
- 通常用于定义静态资源
- 静态资源:可以供很多个对象使用的公共的资源,而这些资源通常都是固定不变的
- 成员变量:
-
static final可以一起使用:用于修饰常量
常量命名:所有字母都大写,多个单词之间用_隔开,常量在声明时进行初始化
注意点:
public static final int A = 4; //建议的 public final static int A = 4; //是常量 - 了解
static和final —重点,难点,常作为面试题出现
面试题
-
以下选项哪个正确(D)
class Test{ public int counter = 0; public static void demo(){ counter++; //编译错误 } public static void main(String[] args){ new Test(); } } A.0 B.1 C.2 D.编译失败
-
以下程序的输出结果是: 3 2 1
class Test{ public static int counter = 0; public int a = 0; public Test(){ add(); } public void add(){ Test.counter++; //1 2 3 a++; //1 2 } public static void main(String[] args){ Test t1 = new Test(); t1.a++; Test t2 = new Test(); t2.a = 1 Test t3 = new Test(); t3.a = 1 System.out.println(Test.counter); 3 System.out.println(t1.a); 2 System.out.println(t2.a); 1 } } 考点:static的成员变量是属于类的,保存在方法区,这个值的变化和对象是否创建无关
-
下列代码运行的结果是(D)。
public class Base { public static final String FOO = "foo"; public static void main(String[] args) { Base b = new Base(); Sub s = new Sub(); System.out.print(Base.FOO); //foo System.out.print(Sub.FOO); //bar System.out.print(b.FOO); //通过对象访问static成员变量 //foo System.out.print(s.FOO); //bar System.out.print(((Base) s).FOO); //foo } } class Sub extends Base { public static final String FOO = "bar"; } A.foofoofoofoofoo B.foobarfoobarbar C.foobarfoofoofoo D.foobarfoobarfoo
注意点:
访问static资源: 1.通过类名访问 -- 推荐使用 2.通过对象访问 -- 可以的,但是不推荐使用
<5>
抽象:abstract-修饰符
-
抽象方法:只有方法的声明,而没有方法体
eg:public abstract void test(); //是抽象方法 public abstract void demo(){} //不是抽象方法,而是方法体为空 强调:没有方法体和方法体为空是不一样的
-
抽象类:
- 若一个类中有抽象方法,这个类一定是一个抽象类;但是,反之,若一个类是抽象类,这个类中不一定有抽象方法。
- 意义:用于提高代码的复用率,提供了一个公共的模板,用于被继承
- 注意点:
- 若一个子类继承了抽象类(有抽象方法),若子类没有对父类中的所有抽象方法进行重写,那么此时这个子类也成为了抽象类
- 抽象类不能实例化的
- abstract和final不可以同时修饰一个类
内部类
-
成员内部类
-
格式:
public class Outter{ private int a = 4; public void test(){ //是否能访问到a --可以 } class Inner{ int b = 5; public void demo(){ System.out.println(this); //内部类对象 System.out.println(Outter.this) //外部类对象 } }
}
-
成员内部类通常只服务于外部类的,对外部不具备可见性,所以通常成员内部类的访问控制修饰符都使用private;
- 成员内部类可以使用所有的访问控制修饰符
-
成员内部类其实是类的成员,可以访问外部类的所有成员,包括私有成员
-
注意点:
-
在成员内部类中this指向了内部类对象,如果要想在内部类中获取外部类对象,可以通过外部类.this这个引用指向了外部类对象
-
若成员内部类的访问控制修饰符是public,外部的其它类如何访问到此内部类对象
public class Outter{ public class Inner{...} } public class Test{ public static void main(String[] args){ //创建Inner内部类对象,如何创建 --通过外部类对象来调用 //Outter out = new Outter(); //Inner inner = out.new Inner(); 等同于: Inner inner = new Outter().new Inner(); } }
-
-
-
匿名内部类:–使用频率非常高 – 一定要会的
-
概念:当某个类的对象只在一个位置使用,那么应该将此类定义为匿名内部类
-
前提:
-
创建的对象所属的类一定继承了某个父类(可以是抽象类,也可以是普通类),或者是实现了某个接口
-
写法:
public abstract class A{ public abstract void demo(); public abstract void demo01(); } public class Test{ public static void main(String[] args){ //创建A类型的对象,这个对象只在这里使用 A a = new A(){ public void demo(){....} }; } }
-
-
注意点:
- 在jdk1.8之前,匿名内部类中若想访问到外面的变量,这个变量只能是final修饰的 – 面试题
可能出现
- 在jdk1.8之前,匿名内部类中若想访问到外面的变量,这个变量只能是final修饰的 – 面试题
-
面试题
-
请问,外部类中的以下哪些可以被成员内部类访问(e)
A 所有静态变量 B 所有常量 C 所有实例变量 D 只有实例常量 E 选择A、B、C均正确
-
以下哪些类的定义是正确的(AB)
A. public final class A{} B. public abstract class A{} C. public abstract final class A{} D. public static class A{} static修饰的三类成员:成员变量,方法,代码块
-
以下哪些选项是正确的(AD)
public class Outer{ public void someOuterMethod(){ //line 3 //创建成员内部类对象 } public class Inner{} //属于外部类对象的 public static void main(String[] args){ Outer o=new Outer(); //Line 8 } } which instantiates an instance of Inner? A new Inner();//At Line 3 B new Inner();//At line 8 C new Outer.Inner();//At line 8 //正确的写法 new Outer().new Inner() D o.new Inner();//At line 8 注意点:成员内部类在外部类中的用法: 1. 在外部类的成员变量位置创建 2. 在外部类的非static方法中直接创建 1. static方法体中能直接访问非static成员吗? 1. 不能 2. 成员内部类是属于外部类对象的,所以static方法体中应该通过外部类对象才可以访问 内部类对象 结论:static方法体中不能直接访问非static成员,反之, 非static方法体中可以访问static成员的,但是不建议这么做 public class Outer{ //成员变量位置创建内部类对象 Inner inner = new Inner(); public void demo(){ //创建内部类对象 Inner inner = new Inner(); } public class Inner{} }
总结:
-
抽象
- 抽象方法:只有方法声明,没有方法体
- 注意没有方法体和方法体为空是不一样的
- 抽象类:
- 与抽象方法的关系
- 注意:
- 子类继承抽象类,要么对其中的抽象方法全部重写,要么这个子类也成为抽象类
- 抽象类不可以实例化
- abstract和final不可以同时修饰一个类
- 抽象方法:只有方法声明,没有方法体
-
内部类:
-
成员内部类
- 注意:
- this指向内部类对象,而外部类.this指向外部类对象
- 若内部类访问控制修饰符非private,外部可以访问,访问方式:
- new Outer().new Inner()
- 等同于
- Outer out = new Outer();
- out.new Inner();
- 成员内部类在外部类中的使用:
- 在外部类成员变量位置直接new Inner()
- 在外部类的非static方法体中直接new Inner()
- 注意:若是在static方法中,应通过外部类对象访问
- 注意:
-
匿名内部类
- 注意:
- 前提:创建的匿名内部类要么继承了父类(可以是抽象类,可以是普通类),要么实现了接口
- 在JDK1.8之前,匿名内部类要想访问外面的变量,此变量必须是final才可以
- 注意:
-
<6>
接口
-
接口:
-
将多个子类中的公共的方法提取出来,若提取的这些方法都是抽象方法,此时就可以将其定义为接口
-
将多个子类中的公共的方法提取出来,若提取的这些方法中有普通方法,也有抽象方法,那么此时出现的父类为抽象类
接口可以说是彻底的抽象
意义:接口就是一个公共的模板,用于被实现的
-
-
接口的实现:implements
- class A implements interface B
扩充知识点:
1. 若实现类没有重写接口中的所有抽象方法,此时这个类就成为了抽象类,这种用法,在java中是经常见得
意义:
class A,B 都实现C接口 ,对其中的抽象方法进行重写,
此时发现,A,B对test方法重写的功能是一样的,
对demo()重写后的功能是不同的.此时就可以这样来做:
interface C: void test(); void demo();
abstract class D implents C{
void test(){
//实现A,B公共的功能
}
}
class A extends D{
重写demo(){}
}
class B extends D{
重写demo(){}
}
2. 接口的出现对java中类的单继承的不足作出的弥补
原因:class A extends B implements C{....}
若A类还想继承另外一个类,不允许,解决办法:
将想继承的的内容放到接口中,就可以在继承B的基础上再实现接口
-
接口中的成员:2种
-
常量
-
抽象方法
接口中为定义的变量提供了默认的修饰符:public static final 接口中为定义的方法提供了默认的修饰符:public abstract
-
-
注意点:
- 接口是可以多实现的
- class A implents B,C,D
- 接口的继承可以是多继承的
- java中类的继承是单继承的,但是接口的继承是可以多继承的
- interface A extends interface B,C,D,E
- java中类的继承是单继承的,但是接口的继承是可以多继承的
- 接口是不可以实例化的,但是可以作为引用类型使用的
- class A implements interface B
- B b = new A();
- class A implements interface B
- 接口的实现和类的继承是可以混合使用的,但是一定是继承在前,实现在后
- class A extends class B implements interface C
- 接口是可以多实现的
-
接口和抽象类的区别 – 经常作为面试题出现
- 接口中只有常量,抽象类中可以有常量,也可以由变量
- 接口中只有抽象方法,抽象类中可以由抽象方法,也可以由普通方法
- 都不可以实例化,但是接口是没有构造方法的,抽象类中有构造方法
- 抽象类之间的继承只能是单继承,接口之间的继承可以是多继承的
面试题:
以下哪个选项是正确的(E)
A. class A extends interface B //错误的
B. class A extends class B,class C //java中类的继承是单继承
C. interface A extends interface B,class C //错误的,注意,接口之间的继承是多继承,接口只能继承接口,不能继承类
D. class A extends class B interface C //错误,类实现接口而不是继承
E. interface A extends interface B,interface C
<7>
多态
-
什么是多态:Java面向对象的三大特征之一
-
为什么/意义:
- 相同类型的引用指向不同的对象时,有不同的实现
-
如何使用:
- 对象造型:
-
向上造型:
- 通过父类的引用指向子类对象
- 注意点:能点什么,看引用的类型
- Super a = new Sub();
a.父类中的成员
- Super a = new Sub();
-
强制造型:
-
引用指向的对象,是该类型–向下造型
//向下造型 class Sub extends Super Super super = new Sub(); //向上造型 通过super访问不到Sub中特有的成员 --缺点 Sub su = (Sub)super; //正确的造型-向下造型 su.子类中特有的成员
-
引用指向的对象,实现了该接口或继承了该类
class C extends class A class C implements interface B A a = new C(); //向上造型 B b = (B)a; //正确的 -- 强制造型 class Airplane extends FlyingObject implements Enemy FlyingObject obj = new Airplane(); Enemy enemy = (Enemy)obj; //正确的
题目:
class A extends C implements D class B extends C C c = new A(); //向上造型 A a = (A)c; //正确的造型 -- 向下造型 D d = (D)c; //正确的造型 --强制造型 B b = (B)c; //错误的造型 总结:强制造型成功与否的关键:看指向的对象类型是否和当前造型成的类型有关
-
-
为了避免造型过程中出现类型转换异常,我们一般都要在转换之前进行判断,通过
- instanceof
-
用法: 引用 instanceof 类型 --boolean
eg: C c = new A();
c instanceof D
-
- instanceof
-
- 对象造型:
内存管理
-
堆:
- 保存的是对象,和实例变量
- 当没有任何引用指向此对象时,此时这个对象就成为了垃圾
- GC:垃圾回收器
- 不定时进行回收
- 但是可以通过System.gc()来建议GC尽快回收
- 对象的生命周期:
- 对象被创建,在堆中存储。之后通过引用可以访问此对象,而当没有任何引用指向此对象时,成为垃圾,等到GC回收
- 实例变量的生命周期
- 实例变量属于对象的,对象被创建,实例变量存储在堆中,随着对象的销毁而被销毁
注意点:内存泄漏:使用完的对象一直没有被回收,建议使用完的对象的引用应置为null
-
栈:
- 保存的是局部变量(参数列表的参数)
- 栈帧:
- 调用某方法时,为此方法在栈中分配了一块区域,叫做栈帧,该方法内部的局部变量都保存在此栈帧中,随着方法的执行结束,栈帧被销毁
- 局部变量的生命周期
- 调用方法时局部变量保存在栈帧中,随着方法的执行结束,随着栈帧而销毁
-
方法区:
- .class字节码文件(方法,static变量)
面试题:
-
以下哪个选项是正确的向下造型(B)
class Sub extends Super class Sub1 extends Super A. Super super = new Super(); Sub sub = (Sub)super; //错误的造型 B.Super super = new Sub(); //向上造型 Sub sub = (Sub)super; //正确的向下造型 C.Super super = new Sub(); //向上造型 Sub1 sub = (Sub1)super; //错误的 D.Sub sub = new Sub(); Sub1 sub1 = (Sub1)sub; //错误的向下造型
-
以下程序的输出结果是什么()
class T{ public static void main(String[] args){ int a = 4; char[] chs = {'a','b','c'}; T t = new T(); t.change(a,chs); 输出 a ? //4 输出 chs[0] ? //g } public void change(int a,char[] chs){ a = 10; chs[0] = 'g'; } }
-
以下程序的输出结果是什么
class SuperCls{ int a = 7; public SuperCls(){ test(); } public void test(){ System.out.println(a); } } class SubCls extends SuperCls{ int a = 8; public SubCls(){ test(); } public void test(){ System.out.println(a); } main: new SubCls(); } 0 8
总结:
- 多态
- 注意:对象造型–强制造型
- 内存管理:
- 考点