1关于Java中的值传递
关于Java中的值传递,其实就是存的是什么值,传出去就是什么值
- 基本数据类型中存的是具体的数值
- 引用数据类型中的存的是地址值
- 类变量的引用定义在栈中,而对象定义在堆中,其中是一个指向的关系
package com.oop.day2;
public class ValueTransferTest {
public static void main(String[] args) {
/*
关于变量赋值
基本数据类型赋的值是变量所保存的数据值
引用数据类型赋的值是变量的地址
形参中的变量也是值传递,因为形参中的变量的生命周期随着方法的调用而创建
随着方法的调用结束而删除
*/
people p1 = new people();
p1.number = 10;
people p2 = new people();
p2 = p1;
p1.number = 22;
System.out.println("p2 :" + p2.number + "\t" + "p1: " + p1.number );
}
}
class people{
int number;
}
输出:22 22
- 对象是可以作为方法参数传递给方法的
//将对象作为参数传递给方法
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
PassObject p1 = new PassObject();
p1.printAreas(c1,5);
System.out.println("circle" + c1.radius);
}
}
class Circle{
double radius;
public double findArea(){
return radius*radius*Math.PI;
}
}
//这里的c.radius就是充分的利用了将对象作为函数参数传递给方法
class PassObject{
public void printAreas(Circle c,int time){
System.out.println("Radius\t\tArea");
for(int i = 1; i <= time; i++){
c.radius = i;
System.out.println(c.radius+"\t\t"+c.findArea());
}
c.radius = time + 1;
}
}
2关于封装性
通过类中属性进行封装,从而使使用者无法直接对其属性进行操作,只可以通过规定好的具体方法去操作
2.1 常见使用
封装性的具体体现,如在类里面加了private属性,不可以在其他的类中对其直接进行(对象.属性)进行操作,而是需要通过设置get()和set()方法进行操作
2.2 权限控制符
- public
权限最大的修饰符,任意包内都可以进行调用 - 缺省
不定义权限控制符的话,默认其为缺省的,在一个包内可以随便调用 - private
权限最低的修饰符,只可以在类中进行随意调用,因为类是最小的控制单位了,想要进行操作的话,必须通过get和set方法进行调用
3构造器
由权限控制符加上类名和大括号构成,可分为有参构造和无参构造,在使用new关键字创建对象实例的时候,构造器被调用
public student()
{}
3.1含义
- 构造器一般用来初始化类对象的属性
- 构造器可以有多个,符合重载的规则
- 可分为有参构造和无参构造,有参构造就是在new对象的时候传入参数即可
3.2构造器规则
- 每个类都有默认的无参构造器,如果用户显示的定义了构造器,那么默认的无参构造器就会消失,需要用户显示定义
- 与类同名
- 无返回值
3.3子类继承父类时的补充
当父类显示的定义了构造器却没有定义无参构造器时,我们用子类继承父类会提示错误,这是因为定义子类后,子类会通过无参构造super()方法默认调用父类中的无参构造方法,而我们在父类中没有定义的话就会出现错误。也可以说是只要子类继承了父类,子类的构造器就要通过super()去调用父类的构造器,如果不通过super()调无参的,就通过super()调有参的也可以。
4 this关键字
- this关键字可以用来修饰或调用:属性、方法、构造器
- this理解为:当前对象 或 正在创建的对象
- 在类的方法中,当形参名与属性名重名时,此时要使用this.变量的方式来代表其是属性而不是形参
- this(形参列表)调用构造器要放在第一行,而且自己不能调自己
5 super关键字
5.1 super关键字调用属性、方法
当子类与父类中所定义的属性名相同时,我们通过super关键字来调用父类中的相关属性,当子类重写了父类中的方法时,我们通过super.方法来调用父类中被重写的这个方法。
5.2 super关键字调用构造器
- 可以通过super(形参列表)的方式显示的调用父类中的构造器
- super(形参列表)或this(形参列表)调用构造器时必须放在构造器的首行,super(形参列表)和this(形参列表)只能二选一
- 如果在构造器中没有显示的给出super(形参列表)或者this(形参列表),则默认是super()去调用父类中的空参构造器
6 this和super调用构造器
- 一个构造器调用另一个重载的构造器用this关键字
- 子类调用父类的构造器用super关键字实现,但更能体现特点的是当子类继承父类之后,子类重写了父类的某个方法,此时我们在子类中还想继续使用父类的方法,这个是时候就需要使用super.方法名来调用父类的方法
7 多态
- 将父类引用指向子类对象或(将子类对象赋给父类引用)就是多态,形式如下。其中student是person的子类。
person p = new student();
- 具体的实现方式为虚拟方法调用,编译器会根据父类引用去调用方法,但是当运行时所执行的是子类中重写的方法。
- 多态的虚拟方法调用只针对于的方法来说,对实例变量则无法调用。
- 若子类重写了父类方法,就意味着子类里的方法彻底覆盖了父类的同名方法,系统不可能将父类的方法转移到子类当中去。
- 对于实例变量则不存在这样的情况,即使在子类中定义了与父类中相同的实例变量,这个实例变量仍然不可能覆盖父类中的实例变量。(也可以说有关于属性的值看编译,即有关于属性,编译运行都看左)
package com.oop.sgg5.exer4;
public class test {
public static void main(String[] args) {
fo a = new fo();
info b = a;
if(a==b){
System.out.println("-----地址值相等-------");
}
System.out.println(a.a);
System.out.println(b.a);
System.out.println(a.ao());
System.out.println(b.ao());
}
}
class info{
int a = 10;
public int ao(){
return this.a;
}
}
class fo extends info{
int a = 20;
@Override
public int ao() {
return this.a;
}
}
- 多态在开发中常用的一种方式:方法中的参数使用的是父类的类型,而在我们实际的调用时所传入的确实子类的对象,这样的话,我们用这个传入的参数所调用的方法即是子类所重写的方法。
- 具体实现的例子可以看下面的代码,我们写具体方法的时候用的参数是传入的图像类,而调用的时候既可以传入圆形也可以传入矩形,而实现的计算面积的方法是圆形或者矩形自己所重写的计算面积的findArea()方法。
package com.oop.sgg5.exer6;
public class GeometricTest {
public static void main(String[] args) {
GeometricTest g = new GeometricTest();
System.out.println(g.eaualsarea(new Circle("blue", 1.0, 2), new Circle("hhh", 1.0, 2.3)));
g.displayGemoticobject(new Circle("hjhh",1.0,5));
g.displayGemoticobject(new myreactangle("ss",1.0,2,3));
}
public boolean eaualsarea(GeometricObjectr c,GeometricObjectr m){
if(c.findarea() == m.findarea()){
return true;
}else{
return false;
}
}
public void displayGemoticobject(GeometricObjectr g){
System.out.println(g.findarea());
}
}
- 关于多态的一道面试题目
package com.oop.sgg5.exer7;
public class InterviewTest1 {
public static void main(String[] args) {
Base base = new Sub();
base.add(1, 2, 3);
// Sub s = (Sub)base;
// s.add(1,2,3);
}
}
class Base {
public void add(int a, int... arr) {
System.out.println("base");
}
}
class Sub extends Base {
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
// public void add(int a, int b, int c) {
// System.out.println("sub_2");
// }
}
这里未加注释时,由于Sub类中的add方法使用数组的方式和可变形参所表达的含义相同,因此此时符合重写规则,则在调用中自然构成了重写,输出sub_1,体现多态行为。
当我们加了注释后,由于将base强制转换成了sub类型,此时调用方法时,有限去匹配类中方法体中能匹配到的方法,则此时输出sub_2,这里就是正常的方法调用。
8包装类
掌握三种主要的转换方法即可
- 基本数据类型 --> 包装类
- 通过构造器 如 Interger f = new Interger(11);
- 通过字符串参数,如 Float f = new Float(“32.3F”); 或 Long l = new Long(“11”);
- 包装类 --> 基本数据类型
- 调用包装类的方法,xxxValue();
- 自动拆箱
- 基本数据类型 --> String类
- String类的valueOf(3.4f)方法
- String类 --> 基本数据类型
调用相应的包装类的parseXxx(String)静态方法 - 包装类 --> String类
- 包装类对象的toString()方法。
- 调用包装类的toString(形参)方法
- String类 --> 包装类
通过字符串参数最合适,Float f = new Float(“32.3F”);
9 Static关键字
Static是静态的,可以用来修饰属性、方法、代码块、内部类
9.1 使用Static修饰属性
- 属性分为了静态属性和实例变量(非静态属性)。我们创建了多个对象,每个对象拥有一套自己的属性,此时一个对象里属性的改变不会影响到别的对象里的属性,这样的成为实例变量(非静态属性),而静态属性是我们创建了多个对象,此时多个对象共享这个属性,当一个对象对其进行操作后,其他对象使用的时候其属性值是改变的。
- 三个关于Static修饰属性的点,第一:静态变量随着类的加载而加载,可以通过类名.属性的方法去调用。第二:静态变量的加载是早于实例对象的创建的。第三:静态变量只会加载一次,存在于方法区中,而实例变量随着对象的创建每次都要在堆中重新加载。
-
类变量 实例变量 类 yes no 对象 yes yes
- 静态方法的举例,如我们经常使用的Math.PI
9.2 使用Static修饰方法
- 随着类的加载而加载,可以通过类.静态方法的方式去进行调用,
静态方法 非静态方法
类 yes no
对象 yes yes - 静态方法中,只能调用静态的属性或者方法,非静态方法中,可以调用静态或者非静态的方法或属性。
9.3 关于Static的注意点
- 在静态方法内,是不可以使用super和this关键字的(有可能对象还未初始化)
- 关于静态方法以及静态属性可以从生命周期的角度去理解。
9.4 开发中,如何确定一个方法是否要声明为Static
- 操作静态属性的方法,通常要声明为Static
- 工具类中的方法,通常也要声明为Static,在使用时可以方便的调用,而不是再去new一个对象
10 代码块
- 代码块的作用:用来初始化类和对象
- 代码块只能用Static进行修饰,分为静态代码块和非静态代码块
- 静态代码块:可以有输出语句,随着类的记载而执行,只执行一次,初始化类的信息。若一个类中定义了多个静态代码块,按照定义的先后顺序进行执行,静态代码块的执行要先于非静态代码块的执行。
- 非静态代码块:可以有输出语句,随着对象的创建而执行,可执行多次,用来初始化对象的信息。 类中如果定义了多个非静态代码块,则按照定义的先后顺序进行执行。
- 对属性可以赋值的位置:默认初始化、显示定义、代码块、构造器、对象.方法或者对象.属性。
- 综合执行顺序分析:
11 关于final关键字
- final可以用来修饰类、方法、变量
- final即“最终”的意思
- final修饰类的时候,代表此类不可继承
- final修饰方法的时候,代表此方法不可被重写
- finla修饰变量,代表两种情况
- final修饰属性:final修饰属性后,就变成了数值常量,不可以再对常量进行操作或者计算,可以进行初始化的位置有:代码块初始化、构造器初始化、显示初始化。
- final修饰局部变量:方法体内修饰为final后不可对变量本身再进行计算,如果把方法中国的形参定义为final后,我们就只能在方法体内还有此形参,不可再赋值
12 Abstract关键字
- abstract即抽象的
- abstract只能用来修饰类和方法
- 抽象类不可以被实例化,只可以被子类继承后实例化子类。但是抽象类中会有构造器,一般供子类对象实例化时使用。
- 抽象方法只有方法声明没有方法体,子类继承抽象类后需要重写抽象类里的抽象方法,如果没有重写父类中的所有抽象方法,则此子类也是一个抽象类,需要abstract修饰。包含抽象方法的类,一定是一个抽象类,但是抽象类中可以没有抽象方法。
- abstract使用中的注意点
- abstract不能用来修饰,属性、构造器等结构
- abstract不能用来修饰私有方法、静态方法、final方法、final的类
- 理解
- 抽象方法即是开发中的一种方式,我们首先写出一个拥有较多公共属性和方法的模板类,之后基于不同的场景,定义不同的子类,既能去共享模板类的公共属性和方法,又可以根据自己的特点去重写方法,是一种较好的开发模式。
13 接口
- 接口使用Interface来定义,在Java中接口和类是并列的两个结构,
- 接口中的成员:
- 在jdk7之前:只能定义全局常量和抽象方法。全局常量:使用public static final进行修饰,但是可以省略不写,但是不要认为接口中没有定义就是变量了。抽象方法:使用public abstract来进行修饰。
- 在jdk8之后,还可以定义静态方法和默认方法
- 接口中不能定义构造器,意味着接口不可以被实例化
- 在Java开发中,接口通过让类去实现(implements)的方式来使用,如果实现类覆盖了接口中的所有抽象方法,则此类可以被实例化。如果实现类没有覆盖接口中的所有抽象方法,那么它仍然是一个抽象类,仍然不可以被实例化。
- Java类可以实现多个接口,弥补了单继承性的局限性。class a extends b implements cc,dd,ee
- 接口之间也可以继承,继承的结果就是抽象方法共用
- 接口也体现了多态性,比如我们把形参定义为接口,而我们在实际使用时,所使用的可能是不同的实现类,这样就具有了很好的通用性。
package com.oop.sgg6.exer6;
interface USB{ //
public void start() ;
public void stop() ;
}
//通过使用usb对象作为形参来实现多态
//虽然定义的是usb类型的,但是具体实现的时候传入的是实现类,所使用的方法也是实现类重写后的方法
class Computer{
public static void show(USB usb){
usb.start() ;
System.out.println("=========== USB 设备工作 ========") ;
usb.stop() ;
}
}
class Flash implements USB{
public void start(){ // 重写方法
System.out.println("U盘开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("U盘停止工作。") ;
}
}
class Print implements USB{
public void start(){ // 重写方法
System.out.println("打印机开始工作。") ;
}
public void stop(){ // 重写方法
System.out.println("打印机停止工作。") ;
}
}
public class InterfaceDemo{
public static void main(String args[]){
Computer.show(new Flash()) ;
Computer.show(new Print()) ;
Computer.show(new USB(){
public void start(){
System.out.println("移动硬盘开始运行");
}
public void stop(){
System.out.println("移动硬盘停止运行");
}
});
}
}
此段代码里,先定义一个usb接口,里面含有start和stop方法,之后定义一个computer类其中有静态方法show(),之后u盘和打印机通过实现接口来实现自己的功能,使用computer类的show方法就实现了多态,最后的是使用了一个匿名对象方法实现。
- 关于Java8之后的补充:
- 知识点1:接口中定义的静态方法,只能通过接口来调用
- 知识点2:通过实现类的对象,可以调用接口中的默认方法
- 知识点3:类优先原则:如果子类(或实现类)继承的父类实现了和接口中同名同参的方法,那么在子类没有重写此方法的前提下,一般优先调用的是父类中的同名同参的方法。
- 知识点4:如果在子类或者(实现类)中调用父类、接口中的方法。通常子类调用父类使用的是super关键字,而实现类调接口使用的是接口名.super.方法
- 知识点5:如果在类继承了多个接口,而这多个接口中定义了同名同参数的默认方法,这会出现接口冲突,报错,这种情况下,我们必须重写方法。
package com.oop.sgg6.exer6;
public class man extends father implements Filial,Spoony{
public static void main(String[] args) {
man m = new man();
m.help();
}
}
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class father{
public void help(){
System.out.println("救wq");
}
}
此时man类中没有去重写help方法,因此输出:救wq,类优先原则。此时如果自己在man类中重写help方法,则输出的是自己重写之后的方法。
14 内部类
- 定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B成为外部类
- 内部类的分类:
- 成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
- 成员内部类的理解:
- 一方面,作为外部类的成员:可以调用外部的结构,可以被static修饰,可以被4种不同的权限修饰符修饰
- 另一方面,作为一个类,类内可以定义方法、构造器、属性等,可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承。可以被abstract修饰。
- 成员内部类:
如何创建成员内部类的对象?(静态、非静态的)
创建静态的Dog内部类实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
创建非静态的Bird内部类实例 (非静态的成员内部类)
Person p = new Person();
Person.Bird bird = p.new Bird(); - 局部内部类的使用
一般是一个方法,需要返回一个接口或者实现类,然后在方法中去定义实现类去实现接口。 - 注意点:成员内部类和局部内部类,在编译以后,都会生成字节码文件。格式:成员内部类:外部类¥内部类名.class。局部内部类:外部类¥数字 内部类名.class
- 在局部内部类的方法中,如果要调用局部内部类所声明的方法中的变量时,则必须声明为final类型的,在JDK7之前需要显示声明,在JDK8之后会自动声明。
15 异常分析
一道综合练习,包括try-catch-finally和throws,以及自定义异常及抛出
package com.oop.sgg6.exer7;
import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
import java.util.Scanner;
/*
编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算
两数相除。
对 数 据 类 型 不 一 致 (NumberFormatException) 、 缺 少 命 令 行 参 数
(ArrayIndexOutOfBoundsException、 除0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。
提示:
(1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。
(2)在main()方法中使用异常处理语句进行异常处理。
(3)在程序中,自定义对应输入负数的异常类(EcDef)。
(4)运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”
(5)Interger类的static方法parseInt(String s)将s转换成对应的int值。 如:int a=Interger.parseInt(“314”); //a=314;
*/
public class EcmDef {
public static int ecm(int a, int b) throws EcDef {
if (a < 0 || b < 0) {
throw new EcDef("不能输入负数");
}
return a / b;
}
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (EcDef e) {
System.out.println(e.getMessage());
}
}
}