Ignite

你不知道的java(三)

2017-07-16

Java基础第二天打卡:每天都过一点java基础,这些看似可以忽略的往往最不可以忽略。

在 Java 程序中,通过类的定义只能实现 单 重继承,但通过接口的定义可以实现 多 重继承关系。

关于继承:

在java中,子类构造器会默认调用super()(无论构造器中是否写有super()),用于初始化父类成员,同时当父类中存在有参构造器时,必须提供无参构造器,子类构造器中并不会自动继承有参构造器,仍然默认调用super(),使用无参构造器。因此,一个类想要被继承必须提供无参构造器。

通过前面我们知道子类可以继承父类的属性和方法,除了那些private的外还有一样是子类继承不了的—构造器。对于构造器而言,它只能够被调用,而不能被继承。 调用父类的构造方法我们使用super()即可。

对于子类而已,其构造器的正确初始化是非常重要的,而且当且仅当只有一个方法可以保证这点:在构造器中调用父类构造器来完成初始化,而父类构造器具有执行父类初始化所需要的所有知识和能力。


1
2
3
4
5
6
7
8
9
void waitForSignal()
{
Object obj = new Object();
synchronized(Thread.currentThread())
{
obj.wait();
obj.notify();
}
}

这题有两个错误的地方,第一个错误是 wait() 方法要以 try/catch 包覆,或是掷出 InterruptedException 才行
因此答案就是因为缺少例外捕捉的 InterruptedException
第二个错误的地方是, synchronized 的目标与 wait() 方法的物件不相同,会有 IllegalMonitorStateException ,不过 InterruptedException 会先出现,所以这不是答案
最后正确的程式码应该是这样:

1
2
3
4
5
6
7
8
9
10
11
void waitForSignal() {
Object obj = new Object();
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
obj.notify();
}
}
java程序的种类

Java程序的种类有:
(a)内嵌于Web文件中,由浏览器来观看的_Applet
(b)可独立运行的 Application
(c)服务器端的 Servlets

Serverlets的工作是:
读入用户发来的数据(通常在web页的form中)
找出隐含在https请求中的其他请求信息(如浏览器功能细节、请求端主机名等。
产生结果(调用其他程序、访问数据库、直接计算)
格式化结果(网页)
设置https response参数(如告诉浏览器返回文档格式)
将文档返回给客户端。

方法通常存储在进程中的代码区

参考链接(https://www.cnblogs.com/liulipeng/archive/2013/09/13/3319675.html)

开发JAVA程序的三个步骤:
  1. 编写JAVA的源程序
  2. 编译程序生成字节码.class文件(java是高级语言,不会直接生成机器语言)
  3. 通过JVM解释执行(通过jvm解释为特定的操作系统如Windows、Linux等能理解的机器码,最终JAVA程序得以执行)
JSP内置对象有:

jsp九大内置对象:

request,response, session,out,page,application,exception,pagecontext,config

基本类型四类八种 其余全是引用类型

四类八种:
整数(byte short int long)
浮点数(float double)
字符型(char)
逻辑性(boolean)
注意大小写,大写的是封装基本类型的引用类型。
byte - Byte
short - Short
int - Integer
long - Long
float - Float
double - Double
char - Charactor
boolean - Boolean

构造方法
  1. 定义:
  • 使用关键字new实例化一个新对象的时候默认调用的方法
  • 构造方法所完成的主要工作是对新创建对象的数据成员赋初值
  1. 使用时需注意:
  • 构造方法名称和其所属的类名必须保持一致
  • 构造方法没有返回值,也不可以使用void
  • 构造方法也可以像普通方法一样被重载
  • 构造方法不能被static和final修饰
  • 构造方法不能被继承,子类使用父类的构造方法需要使用super关键字
类构造方法中调用父类构造方法用super,调用本类其他的构造方法用this。 super和this都只能位于类构造方法的第一行,不能同时存在

以下输出结果是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Example {
String str = new String("good");
char[] ch = { 'a', 'b', 'c' };

public static void main(String args[]) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.print(ex.ch);
}

public static void change(String str, char ch[])
{
str = "test ok";
ch[0] = 'g';
}
}

good and gbc

说法一:

  1. 概念:

java传参只有按值传递(也就是把实参的值拷贝给形参,这个值可以是普通的数值,也可以是地址值),java中的对象只能通过指向它的引用来操作,这个引用本身也是变量,不要与C/C++中的传值与传址混淆了,java中没有显式的指针。

分析:change函数被调用时,第一个形参str接收了类的成员变量str的值(虽然名称都是str,但是却是两个独立的String类型的引用变量),注意这两个str自身都是变量且都指向了堆内存中的String对象"good",当我们在change函数内部将str指向了另一个String对象"test ok"后,类的成员变量str仍然保持指向"good",所以最终打印出来就是"good";对于第二个形参ch,它也是接收了类的成员变量ch的值拷贝,这一点和str没有差别,即两个ch都指向了字符数组{ ‘a’, ‘b’, ‘c’ }的首地址,但是ch[0]表示的是字符数组中’a’的地址,修改了它也就修改了字符数组的第一个元素,这个改变在change函数返回之后也会存在。所以本题中两个形参传参的本质区别在于,修改str只是将形参指向了新的对象,对外部的实参没有任何影响,而修改ch[0]是实实在在的修改了字符数组的首元素。

  1. 拓展
  • 可以试验一下,在Example中再定义一个字符数组char[] ch2={‘d’};然后在change函数中把ch[0] = ‘g’;这句改成ch=ch2;,那么就会和str传参一样的,change函数返回后不会对类的成员ch有任何影响。
  • 本题和“String类是一个final类,不能被继承”以及“String底层的字符数组被声明为private final char value[];所以其值不能被修改”这些String的特性无关。
  • 我们平时交换数组中的两个元素时,一般定义swap方法为 void swap(int[] a, int i, int j),想想看为什么能达到目的?如果不使用数组,能实现交换吗?数组中存放的不是基本类型变量而是引用类型变量呢?

说法二:

其实都是引用传递,只是因为String是个特殊的final类,所以每次对String的更改都会重新创建内存地址并存储(也可能是在字符串常量池中创建内存地址并存入对应的字符串内容),但是因为这里String是作为参数传递的,在方法体内会产生新的字符串而不会对方法体外的字符串产生影响。

关于String

String str1 = “hello”;
String str2 = “he” + new String(“llo”);
System.err.println(str1 == str2);

解释:String str1 = “hello”;这里的str1指的是方法区的字符串常量池中的“hello”,编译时期就知道的; String str2 = “he” + new String(“llo”);这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。
如果用str1.equal(str2),那么返回的是True;因为两个字符串的内容一样。

自动拆装箱
  1. 基本型和基本型封装型进行“==”运算符的比较,基本型封装型将会自动拆箱变为基本型后再进行比较,因此Integer(0)会自动拆箱为int类型再进行比较,显然返回true;
  2. 两个Integer类型进行“==”比较,如果其值在-128至127,那么返回true,否则返回false, 这跟Integer.valueOf()的缓冲对象有关,这里不进行赘述。
  3. 两个基本型的封装型进行equals()比较,首先equals()会比较类型,如果类型相同,则继续比较值,如果值也相同,返回true
  4. 基本型封装类型调用equals(),但是参数是基本类型,这时候,先会进行自动装箱,基本型转换为其封装类型,再进行3中的比较。

解释:

int a=257;
Integer b=257;
Integer c=257;
Integer b2=57;
Integer c2=57;
System.out.println(a==b);
//System.out.println(a.equals(b)); 编译出错,基本型不能调用equals()
System.out.println(b.equals(257.0));
System.out.println(b==c);
System.out.println(b2==c2);

因此上面的代码的结果因此为 true, false, false, true

java多线程间的通讯之等待唤醒机制
1. wait()、notify()和notifyAll()是 Object类 中的方法
  1. wait()、notify()和notifyAll()是 Object类 中的方法
  2. 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
  3. 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程
  4. 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
  • wait()方法被某个对像调用时,相当于让当前线程交出此对象的monitor,然后进入等待状态,
    等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从
    而让其他线程有机会继续执行,但它并不释放对象锁);
  • notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象
    的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
    同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用
    notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
  • nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。
  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(); Condition中的signalAll()对应Object的notifyAll()
一个选择题教你学方法重写

如果一个接口Cow有个方法drink(),有个类Calf实现接口Cow,则在类Calf中正确的是? ©

  • A. void drink() { …}
  • B. protected void drink() { …}
  • C. public void drink() { …}
  • D. 以上语句都可以用在类Calf中
假设 A 类有如下定义,设 a 是 A 类的一个实例,下列语句调用哪个是错误的?()

Class A
{
Int i;
Static String s;
Void method1(){}
Static void method2(){}
}

解析:静态成员和静态方法,可以直接通过类名进行调用;其他的成员和方法则需要进行实例化成对象之后,通过对象来调用。
所以a.method1();(对)、A.method2();(对)、 A.method1();(错)

java 多态

抽象类或者接口根本无法实例化

哪些情况可以终止当前线程的运行?
  • 优先级高的并不一定会马上执行。
  • sleep方法会阻塞一个线程并不会终止
  • 创建一个新的线程时也不会终止另一个线程
  • 当抛出一个异常后程序会结束,所以线程也会被终止
java不允许单独的方法,过程或函数存在,需要隶属于某一类中; java语言中的方法属于对象的成员,而不是类的成员。不过,其中静态方法属于类的成员
equals()和==的区别:

String s1=“abc”+“def”;//1
String s2=new String(s1);//2
if(s1.equals(s2))//3
System.out.println(".equals succeeded");//4
if(s1==s2)//5
System.out.println("==succeeded");//6

equals()比较的是字符串的内容,==比较的是内存地址值

s1存放在常量池中 s2存放在堆区中 内存地址值是不一样的

垃圾回收机制,如题

static String str0=“0123456789”;
static String str1=“0123456789”;
String str2=str1.substring(5);
String str3=new String(str2);
String str4=new String(str3.toCharArray());
str0=null;
假定str0,…,str4后序代码都是只读引用。
Java 7中,以上述代码为基础,在发生过一次FullGC后,上述代码在Heap空间(不包括PermGen)保留的字符数为()

垃圾回收主要针对的是堆区的回收,因为栈区的内存是随着线程而释放的。
堆区分为三个区:年轻代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法区)。
年轻代:对象被创建时(new)的对象通常被放在Young(除了一些占据内存比较大的对象),经过一定的Minor GC(针对年轻代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。年老代:就是上述年轻代移动过来的和一些比较大的对象。Minor GC(FullGC)是针对年老代的回收
永久代:存储的是final常量,static变量,常量池。
str3,str4都是直接new的对象,而substring的源代码其实也是new一个string对象返回,如下图:
经过fullgc之后,年老区的内存回收,则年轻区的占了15个,不算PermGen。所以答案为15。

java设计模式之单例模式

参考博客(https://blog.csdn.net/jason0539/article/details/23297037/)

java stack 和heap 的区别

参考博客(https://blog.csdn.net/wl_ldy/article/details/5935528)

关于iterator(迭代器)

迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:

  1. 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
  2. 使用next()获得序列中的下一个元素。
  3. 使用hasNext()检查序列中是否还有元素。
  4. 使用remove()将迭代器新返回的元素删除。

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

迭代器应用:

list l = new ArrayList();
l.add(“aa”);
l.add(“bb”);
l.add(“cc”);
for (Iterator iter = l.iterator(); iter.hasNext()😉 {
String str = (String)iter.next();
System.out.println(str);
}
//迭代器用于while循环
Iterator iter = l.iterator();
while(iter.hasNext()){
String str = (String) iter.next();
System.out.println(str);
if(str.equals(“aa”)){
System.out.println("----------------------");
iter.remove();
System.out.println(l.toString);
}
}
输出结果:
aa
bb
cc


[bb, cc]

下面给出一个例题:

Iterator it = list.iterator();
int index = 0;
while (it.hasNext())
{
Object obj = it.next();
if (needDelete(obj)) //needDelete返回boolean,决定是否要删除
{
//如何在这里可以,在Iterator遍历的过程中正确并安全的删除一个list中保存的对象
}
index ++;
}

答案:it.remove();

解释:
如果在循环的过程中调用集合的remove()方法,例如:

for(int i=0;i<list.size();i++){
list.remove(…);
}

循环过程中list.size()的大小变化了,就会导致导list中的元素删除不完全。
所以,如果你想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。

Spring 支持 7 种事务传播行为:
  • PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
  • PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。
集合小结
  1. List(有序、可重复)
    List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。
  2. Set(无序、不能重复)
    Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。
  3. Map(键值对、键唯一、值不唯一)
    Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。

集合类汇总(https://www.cnblogs.com/leeplogs/p/5891861.html)

replaceAll() 方法
1
2
3
4
public static void main (String[] args) {
String classFile = "com.jd.". replaceAll(".", "/") + "MyClass.class";
System.out.println(classFile);
}

输出结果:///////MyClass.class

由于replaceAll方法的第一个参数是一个正则表达式,而".“在正则表达式中表示任何字符,所以会把前面字符串的所有字符都替换成”/"。如果想替换的只是".",那么久要写成"\.".

对象序列化的描述

使用ObjectOutputStream和ObjectInputStream可以将对象进行传输.

声明为static和transient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据。

在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。

jsp中静态include和动态include的区别
  1. 动态 INCLUDE 用 jsp:include 动作实现 <jsp:include page=“included.jsp” flush=“true” /> 它总是会检查所含文件中的变化 , 适合用于包含动态页面 , 并且可以带参数。各个文件分别先编译,然后组合成一个文件。

  2. 静态 INCLUDE 用 include 伪码实现 , 定不会检查所含文件的变化 , 适用于包含静态页面 <%@ include file=“included.htm” %> 。先将文件的代码被原封不动地加入到了主页面从而合成一个文件,然后再进行翻译,此时不允许有相同的变量。

  3. 两者的区别:

    一: 执行时间上 :

    <%@ include file=“relativeURI”%> 是在翻译阶段执行
    <jsp:include page=“relativeURI” flush=“true” /> 在请求处理阶段执行 .

    二: 引入内容的不同 :

    <%@ include file=“relativeURI”%>
    引入静态文本 (html,jsp), 在 JSP 页面被转化成 servlet 之前和它融和到一起 .
    <jsp:include page=“relativeURI” flush=“true” /> 引入执行页面或 servlet 所生成的应答文本 .

变量引用
1
2
3
4
5
6
7
8
9

public class Replaces {
public int x;
public static void main(String[] args) {
System. out. println("Value is" + x);

}

}

改正:实例化Replaces 或 public static int x;

原因:非静态成员只能被类的实例化对象引用,因此这里在静态方法中访问x会造成编译出错

AOP和OOP

AOP 和 OOP的区别:

  1. 面向方面编程 AOP 偏重业务处理过程的某个步骤或阶段,强调降低模块之间的耦合度,使代码拥有更好的移植性。
  2. 面向对象编程 (oop) 则是对业务分析中抽取的实体进行方法和属性的封装。
    也可以说 AOP 是面向业务中的动词领域, OOP 面向名词领域。
    AOP 的一个很重要的特点是源代码无关性,也就是说如果我们的系统中引用了 AOP 组件,即使我们把该组件去掉,系统代码也应该能够编译通过。要实现这一点,可以使用动态 proxy 模式。
类的初始化顺序

静态优先,父类优先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class HelloA{
public HelloA()
    {
        System.out.println("I’m A class ");
    }
    static
    {
    System.out.println("static A");
    }
}
public class HelloB extends HelloA{
    public HelloB()
    {
        System.out.println("I’m B class");
    }
    static{
        System.out.println("static B");
    }
    public static void main (String[] args){
        new HelloB();
    }
}

输出:static A static B I’m A class I’m B class

ResultSet中记录行的第一列索引为1
JSP

A JSP脚本(Scriptlet),是Java代码块。

C JSP表达式(expression),表达式是一个有返回值的式子,它返回的结果将由out.print()进行输出。

所以jsp表达式写法:
<%= expression %>

c语言&&和||的短路性质分析:
(https://blog.csdn.net/yhj110911119/article/details/52436085)

Tags: Java
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章