Java笔试面试题整理第七波

本文详细探讨了Java中的IO/NIO基础知识,包括super的作用、构造方法、transient关键字、继承规则、for与foreach的区别,以及IO与NIO的阻塞与非阻塞特性。重点讲解了阻塞IO、非阻塞IO、同步IO和异步IO的差异,并通过实例展示了Java NIO的使用,包括Buffer对象的关键属性。最后,提供了NIO服务端和客户端的示例代码。
摘要由CSDN通过智能技术生成

1、super的作用

    在Java中super指代父类对象(直接父类),也就是说,super相当于是一个直接new出来的父类对象,所以可以通过它来调用父类的那些非private修饰的变量、方法(对于我们普通new出来的对象来说,也就只能访问那些非private的成员变量、方法了,这里的访问是指通过“对象名.变量名或方法名”的形式)。所以,super这个对象也就是一个普通对象,同样遵循访问控制修饰符的准则
    然而,对于子类来说,子类通过继承就直接拥有了父类的非private变量、方法,也就可以在子类中直接使用,再加一个super来修饰,岂不是显得有点多余了?正常情况下来说,是有点多余了(但是可以明确提示我们这是调用的父类变量或方法),但super关键字主要是用在以下两种情况中:
(1)发生了重写的情况
    重写也分为两种情况,一个是重写了父类的方法;一个是重写了父类的成员变量;
重写父类方法的情况
public class A {
    String name = "lly";
    protected void getName(){
        System.out.println("父类getName->"+ name);
    }
}
public class B extends A {
    String nameB = "llyB";
    @Override
    protected void getName() {
        System.out.println("子类getName->"+nameB);
        super.getName();
    }
    public static void main(String[] args) {
        B b = new B();
        b.getName();
    }
}
打印如下:
子类getName->llyB
父类getName->lly

在子类B中,我们重写了父类的getName方法,如果在重写的getName方法中我们去调用了父类的相同方法,必须要通过super关键字显示的指明出来
如果不明确出来,按照子类优先的原则,相当于还是再调用重写的getName()方法,此时就形成了死循环,执行后会报java.lang.StackOverflowError异常。

重写父类变量的情况
我们将B类简单改造一下:
public class B extends A {
    String name = "llyB";
    @Override
    protected void getName() {
        name = super.name;
        System.out.println("子类getName->"+name);
    }
    public static void main(String[] args) {
        B b = new B();
        b.getName();
    }
}
此时子类B中有一个和父类一样的字段(也可以说成父类字段被隐藏了),为了获得父类的这个字段我们就必须加上super,如果没有加,直接写成name = name;不会报错,只是会警告,表示此条语句没有任何意义,因为此时都是访问的子类B里面的那么字段。

我们通过super是不能访问父类private修饰的变量和方法的,因为这个只属于父类的内部成员,一个对象是不能访问它的private成员的。

(2)在子类的构造方法中
编译器会自动子类构造函数的第一句加上 super()来调用父类的无参构造器;此时可以省略不写。如果想写上的话必须在子类构造函数的第一句,可以通过super来调用父类其他重载的构造方法,只要相应的把参数传过去就好。

因此,super的作用主要在下面三种情况下:
1、调用父类被子类重写的方法;
2、调用父类被子类重定义的字段(被隐藏的成员变量);
3、调用父类的构造方法;
其他情况,由于子类自动继承了父类相应属性方法,关键字super可以不显示写出来。

2、关于构造方法

如果一个类中没有写任何的构造方法JVM会生成一个默认的无参构造方法。在继承关系中,由于在子类的构造方法中,第一条语句默认为调用父类的无参构造方法(即默认为super(),一般这句话省略了)。所以当在父类中定义了有参构造函数,都是没有定义无参构造函数时,IDE会强制要求我们定义一个相同参数类型的构造器。这也是我们在Android中自定义组件去继承其他View是经常被要求定义几个构造函数的原因。

以下子类B的情形是错误不能通过编译的:
public class A {
    public A(String s){  }
}
public class B extends A {    //编译错误,JVM默认给B加了一个无参构造方法,而在这个方法中默认调用了super(),但是父类中并不存在该构造方法
    String name = "llyB";
}
public class B extends A {    //同样编译错误,相同的道理,虽然我们在子类中自己定义了一个构造方法,但是在这个构造方法中还是默认调用了super(),但是父类中并不存在该构造方法
    String name = "llyB";
    public B(String s){}
}
此时就需要显示的去调用父类构造方法了,如下:
public class B extends A {    //正确编译
    String name = "llyB";
    public B(String s){
        super(s);
    }
}
所以,只要记住, 在子类的构造方法中,只要里面没有显示的通过super去调用父类相应的构造方法,默认都是调用super(),即无参构造方法,因此要确保父类有相应的构造方法

3、transient关键字用法

当用transient关键字修饰一个变量时,这个变量将不会参与序列化过程。也就是说它不会在网络操作时被传输,也不会再本地被存储下来,这对于保护一些敏感字段(如密码等...)非常有帮助。

当我们一个对象实现了Serializable接口,这个对象的所有字段和方法就可以被自动序列化。当我们持久化对象时,可能有一个一些特殊字段我们不想让它随着网络传输过去,或者在本地序列化缓存起来,这时我们就可以在这些字段前加上transient关键字修饰,被transient修饰变量的值不包括在序列化的表示中,也就不会被保存下来。这个字段的生命周期仅存在调用者的内存中
如下一个例子:
public class UserBean implements Serializable{
    private static final long serialVersionUID = 856780694939330811L;
    private String userName;
    private transient String password;    //此字段不需要被序列化
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
测试类:
public class Test {
    public static void main(String[] args) {
        UserBean bean = new UserBean();
        bean.setUserName("lly");
        bean.setPassword("123");
        System.out.println("序列化前--->userName:"+bean.getUserName()+",password:"+bean.getPassword());

        //下面序列化到本地
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("e:/userbean.txt"));
            oos.writeObject(bean);//将对象序列化缓存到本地
            oos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            if(oos != null){
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //下面从本地反序列化缓存出来
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/userbean.txt"));
            bean = (UserBean) ois.readObject();
            ois.close();
            System.out.println("反序列化后获取出的数据--->userName:"+bean.getUserName()+",password:"+bean.getPassword());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
打印结果如下:
序列化前--->userName:lly,password:123
反序列化后获取出的数据--->userName:lly,password:null

看到,password反序列化后的值为null,说明它没有被保存到本地,因为我们给它加上了transient修饰。

注意:
(1)从上面可以看到,被transient修饰后,反序列化后不能获取到值;
(2)transient只能修饰变量,不能修饰方法,修饰我们自定义的对象变量时,这个对象一定要实现Serializable接口;
(3)static变量不能被序列化,即使它被transient修饰。因为static修饰的变量是属于类的,而我们序列化是去序列化对象的。
上面的例子中如果给userName加上static修饰,反序列化后依然能够获取到值,但是这个时候的值是JVM 内存中对应的static的值,因为static修饰后,它属于类不属于对象,存放在一块单独的区域,直接通过对象也是可以获取到这个值的。上面的第三点依然成立。

4、下面哪些类可以被继承

下面哪些类可以被继承? Java.lang.Thread、java.lang.Number、java.lang.Double、java.lang.Math、 java.lang.ClassLoader
A、Thread    B、Number    C、Double    D、Math    E、ClassLoader

正确答案:ABE
1、对于Thread,我们可以继承它来创建线程;
2、Byte、Short、Integer、Long、Float、Double几个数字类型都是继承自Number;
3、Byte、Short、Integer、Long、Float、Double、Boolean、Character几个基本类型的包装类,以及String、Math它们都是被定义为public final class,因此这几个都不能被继承;
4、我们可以自定义类加载器来实现加载功能,因此ClassLoader是可以被继承的。

5、for和foreach遍历的比较

    针对for和foreach两种循环,我用10万、100万、1000万级别大小的List集合数据进行了测试一下,发现整体来说foreach的执行时间和普通for循环的时间差别不大。对于这两个的比较,我网上查了一下,有说foreach效率高一点的,有说for效率高一点的。
    网上说for效率高的,主要是在针对遍历集合类的数据时,for表现要稍微好一点,因为foreach内部它使用的是Iterator迭代器的执行方式。因此下面分两种结构来测试:
先来测试对数组遍历的时间快慢,数组里面保存1000万的随机数。如下:
  • 29
    点赞
  • 130
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值